本稿は自作エディタをつくる Advent Calendar 2016の9日目です、レポジトリはこちら
このアドベントカレンダーは、コンソール上で動くエディタを25日間で1から自作することを目標としています。
unsigned char
qiitaのコメントで親切な方に教えてもらったのですが、
charはunsigned charでもsigned charでもない型で、
処理系によってunsignedかsignedか決まるみたいです。
まじか・・・、決まってないんだ・・・。自分の中では衝撃的な事実です。
あとで困らないように(7日目で困っていたし)、色んなところをunsigned charにしていきます。
あわせて、intにしていたところのうち、負の値にならないところをunsigned intにしたりしていきます。
これは、負の値を入れようとすると怒られるかな?っていう期待があったのですが、自分の環境だと怒られずにキャストされます。
あと、char -> intも怒らずに勝手にキャストされます。
charとunsigned charは勝手にキャストせずに警告してくれるので、なんだか不思議な挙動です。
あんまりcharを文字専用の型だと思わない方がいいのかな。
教えていただきありがとうございます(๑•̀ㅂ•́)و✧
文字の幅を決める
自分が調べた範囲においては、コンソールで頑張る限りどうも文字コードから文字の表示幅を取得できない気がしています。
エンドユーザはみんな半角が1文字・全角が2文字分の幅で表示されるフォントを使っているんだ、ということにして
UTF8で2バイト以上ある文字の幅は全部2文字
に、とりあえずしました。
前提と結論にギャップがある気がしますが、文字コードは調べるほどわからなくなっていくので。。。
でも、ちゃんと調べたあとに直せるように判定を関数として切り出しておきます。
文字コードってみんなどうやって勉強してるんでしょうか?仕様書読んでるのかな。
文字列の幅・高さを取得する
上の強引な仮定にしたがって、芋づる式に文字列全体の幅や高さを取得できるようにしていきます。
高さ(1行が長すぎて1回折り返した時、高さを2と呼ぶ)は毎回表示のたびに計算しなおしていますが、
コンソールの幅が変わらなければ計算し直さなくてもいいはずなのでキャッシュするようにしました。
static int
に入れると、関数を抜けても値が保持されます。他の言語だと、シングルトンとか呼ぶやつですね。
カーソルを作る(仮)
とりあえず構造体を作ります。
typedef struct _cursor {
unum position_x;
unum position_y;
} cursor;
それをコンテキストと呼んでいるMVCのM担当のところに追加します。
入れたものの・・・、色々足りてないですね。
キーボード操作を作る
3日目に試してみて、5日目に設計を考えていたあたりで捨ててしまったキーボード操作部分を
gitの歴史から引っ張り出して実装します。
このときに、6日目から8日目で作ったマルチバイト対応も合わせて入れます。
値渡しと参照渡しを間違えたりしましたが、思ったよりすんなり進みます。
コマンドを作る
キーボード操作を内部DSLとして変換する感じです。
enum CommandType {NONE, UP, DOWN, LEFT, RIGHT, EXIT};
typedef struct _command {
enum CommandType command_key;
mbchar command_value;
} command;
変換する部分も、仮置きぐらいの感じで適当につくります。
さくさく進みます。
本文表示部分をつくる
もしもgithubまでみている人がいたら、void debug_print_text(context context)
っていう関数の存在に気づいたかもしれません。
そのdebug部分を取り除いて、本文表示部分をつくります。
本文を行ごとに双方向リストで、行は文字列を単方向リストでつないだ構造になっているのですが、リンクを辿って表示する感じです。
(2日目・3日目で作った部分ですね)
カーソルがきたら1日目で調べた、コンソール上の色を変える制御文字をだして、次の文字でリセットします。
カーソルの幅について
いま気づいたのですが、コンソール上ではないエディタは、カーソルの幅が0文字です。入力したい直前の位置に縦の棒が出ています。
コンソール上で同じことをするのは難しいのでviやnanoのように色をつけたのですが、カーソルの見た目の幅が1文字です。
ここで、Shiftと十字キーみたいな操作で1文字選んだとすると、
- コンソール上ではないエディタは選択していない・1文字選択状態を区別できる
- コンソール上で動くエディタは選択していない・1文字選択状態を区別できない?
色を変えるとか、工夫が要りそうですね。
カーソルの色を返す関数名をcolor_cursor_normal
にして、選択できるようになったら考えることにします。
カーソルの実装
十字キーは難しいので(後述)、u/d/l/rキーで上下左右に、eキーで終了することにします。
void command_perform(command command, context *context)//PUBLIC;
{
switch(command.command_key)
{
case UP:
(*context).cursor.position_y -= 1;
break;
case DOWN:
(*context).cursor.position_y += 1;
break;
case RIGHT:
(*context).cursor.position_x -= 1;
break;
case LEFT:
(*context).cursor.position_x += 1;
break;
case EXIT:
exit(EXIT_SUCCESS);
break;
case NONE:
break;
}
}
このままだとカーソル位置をマイナスにできたりしますが、とりあえず動きました。
今日は結構進んだ気がします(๑˃̵ᴗ˂̵)و ヨシ!
十字キーの謎
自分の環境でpritfデバッグしたところ、十字キーは
char up[3] = {0x1B,0x5B,0x41};
char down[3] = {0x1B,0x5B,0x42};
char right[3] = {0x1B,0x5B,0x43};
char left[3] = {0x1B,0x5B,0x44};
になるはずです。これは8日目に書いた後続文字判定だと、UTF8として不正な文字です。
たぶん十字キーはUTF8ではない文字コードか、もしくは制御文字でunicode表に載ってないってことだと思います。
出力と入力で使用する後続文字判定関数を分けて、決め打ちで十字キーの後続判定するしかないんだろうか・・・?
もうちょっと調べてから結論を出したいところ。
今日のまとめ
- やっつけで文字の幅を決めた
- 本文・カーソルが表示できるようになった
- 不完全ながら、カーソル移動ができるようになった
↑だいぶエディタっぽさが出てきたのでは!?
このカレンダーを始めるまではもっとUIとか操作っぽいところが難しいと思っていましたが、ずっと文字コードに悩んでますね。
ASCIIコードに絞って、もっと動くところまでもっていってから悩むべきだったのかもしれません、いまさらだけど。
いつのまにかD社のアドベントカレンダーが消えていました、読みたいやつあったのに残念です。