Rhinocs
Rhinocs
- karino2/Rhinocs: Rhino backed Android editor only for keyboard.
- karino2/RhinocsSKK: SKK port for Rhinocs Rhinocs用のSKK移植
RhinoをバックエンドにしたAndroidのキーボード専用エディタ。
コンセプト
キーボード前提(必須)のAndroidのエディタ。
AndroidのIME周辺のキーボードの扱いが好きになれないので、全部キーコードを自前でハンドルするエディタを作りたい。 テキスト入力専用で快適に長文が入力出来る感じの。中途半端なタッチ対応が諸悪の根源と思っているので、 タッチでは全く使えない代わりにキーボードで快適、というものにしたい。 自分のBOOXをポメラっぽく使いたい。 ソフトキーのIMEは使わずにキーボード専用のIMEを自作する(追記:SKKを移植した)。
Rhinoをバックエンドにしたemacsみたいなエディタを考えている。 ということで名前はRhino backendのemacsっぽいAndroid用エディタ、Rhinocsとした。 ただしActivityのリサイクルがあるのでemacsのようにずっとメモリ上にあるという前提の構造には出来ないからそこは違ってくると思う。
上記を満たしつつ、ちゃんとAndroidのアプリとして正しく振る舞うエディタが欲しい。PCのアプリの移植のような無理やりAndroidで動かすものではなく、Androidのアプリとして自然に振る舞いたい。 Activityのリサイクル、SAFなどを考えたエディタであって欲しい。
2026-05-20 (水) 時点での進捗
- Rhinoにキーイベントを渡してキーマップに登録した関数を実行する事で動くエディタ
- SKKを移植して動く
- SAF経由でのファイルのオープンと保存
- コピー・ペースト、eval_regionなどが動く
ToDo
優先度の高い順に思いついた事を書いておく。
- ステータスバー(SKKの現在のモードを知りたいので)
- ミニバッファ
- バッファ切り替え(うっかり作業が消えてしまわないように)
- undo
作業ログ
とりあえず何をやったかを書いておく所。
2026-05-21 (木)
BOOXでC-Space効かない問題の対処
BOOXでC-Spaceが横取りされる問題と格闘。
KEY_UPは来るのでdownが来てなくてupが来る時だけ特別扱い。 アットマークがShiftとの同時押しじゃなければC-@でも良かったんだがなぁ。 しかもC-@はなんかIME切り替えが動いてしまう。
デバイスごとの保存を考える
Rhinocsはなるべく見えない所(data下)にものを保存しない、をコンセプトにしているが、これだとファイルのヒストリのような、デバイスごとに保存したいものをjsから保存する時にsyncthingで共有されてしまって困る。
デバイスごとのIDは簡単にはとれなさそうなので、手動で設定する事にしたい。
APIとしてはSharedPreferenceに文字列を読み書き出来ればいいだろう。それを使ってデバイスごとに一回手動で set_device_id("BOOX") とかをevalする感じにすればいいかな、と思っている。
すると、 /storage/per_device/BOOX/file_history/history.json とかに保存する感じになればいいだろうか。
get_per_device_storage_dir()とかで取れればいいかな。storageって名前がいまいちだな。なんかいい名前あるだろうか?でもstoreではplay storeと被るしなぁ。 storageでいいか。
デバイスごとの保存を実装
以下を実装した。
- join_path
- put_pref_string
- get_pref_string
- set_device_id
- get_device=id
- get_per_device_storage_dir
ヒストリは期待通り動いてそう。
SKKの未知単語登録の実装
innerSKKは使い方が良く分からない上に普通のIMEで変換したものを入力する方がAndroid的には普通だと思うので、 ダイアログで登録するように変更した。
次はユーザー辞書の保存だ。
SKKのユーザー辞書の保存
元のコードほぼそのままでファイルを保存して動くようにはなった。 保存フォーマットには無駄にwordとかついているが、まぁまずはいいか。
これでデバイスごとに辞書を保存するようになったぜ。
あとはステータスバーあたりを終えればSKKのコードは安定するかな。
2026-05-20 (水)
マーカー仕様検討
eval-regionやコピペのためにマーカーが欲しくなる。既存実装を調査>エディタ調査
elipsの関数名を参考に考える。
GNU Emacs Lispリファレンスマニュアル: Markers
マーク的なものは最小限にして基本的にはマーカーのみを揃える感じで。しばらくmark-ringとかはやらない。 以下を用意するか。
- mark_marker
- set_marker
- marker_position
mark関連の唯一の関数がmark_marker。 make_markerはしばらく提供せず、mark_markerで唯一のmarkerを取得する感じにする。 markなどはmark-markerとmarker-positionなどを使ってjsレイヤーで実装する。
この3つを実装するには以下をすればいいか。
- Bufferにmarkerのリストをぶら下げる
- insertとかdeleteとか必要な所でadjustの実装を追加
- js側に見せるメソッドを追加
マーカーを実装
C-SpaceとC-x C-xはとりあえず動いてそうなのを確認。あとはbuffer_substringを実装すればeval_regionはevalで実装出来るな。
buffer_substringとeval_regionを実装
buffer_substringを作り、とりあえずeval_regionを実装してみる。 ミニバッファがまだ無いのでC-jにバインドしておく。 これでセルフ開発が捗るぜ。 まだexceptionハンドルしてないと落ちてしまう事もあるが。
クリップボード関連を実装してコピペなどを実装
コピーはクリップボードにコピーするようにする。なんかフローティングが出てきてダサいが、まぁいいか。 そしてそれらを使ってkill-regionとかも実装してとりあえずコピペ系が揃う。
最初のリリースまでの道のりを考える
現状は最初にいろいろ設定されている現状なので、ちょっと第三者には敷居が高い。 emacsユーザーくらいならすぐに分かる程度くらいまでは敷居を下げたい気がする。
まずSKKとエディタのバージョンが揃ってないとすぐに動かなくなるので、 それが一段落してからにしたい気はする。
- ステータスウィンドウ
- ユーザー辞書
この二つが揃えばSKKの必要なAPIは揃うな。
次にswitch-bufferで前のバッファには戻れるようにしたい。
あとはsave-bufferでまだ保存してなれば名前をつけて保存、はやりたいか。
この位出来たらとりあえず第三者でも試せるかな。
さらにミニバッファとsplit-windowが出来たら広く宣伝してもいいかもしれない。
この辺まで行けば最初のリリースかな。 まとめておく。
- ステータスウィンドウ
- SKKのユーザー辞書
- switch-buffer
- save-buffer-as
- ミニバッファ
- split-window
2026-05-19 (火)
js相対のreadを廃止していつもpackageルートからに
jsのファイルを読み込んでevalする時にそのファイルのパスを覚えておいてread_textとかではそこからの相対で読んでいたのだが、 これだと他のファイルで定義した関数を呼ぶ時に定義元ファイルでは無く呼び出し側の相対になってしまう事に気づいた。
Scopeオブジェクトなどを定義すればスコープを作る事は出来るが、どうもGlobalObjectのメソッドを呼ぶ時はthisObjをscopeとして渡してしまうようで、 runで渡しているscopeが来ない。
うーん、いつもパッケージのルートからの絶対パスで指定するようにしようかなぁ。それしか無い気がする。>そうした
ヒストリをなんとなく実装
見た目はダサいがとりあえず0-9の数字キーでヒストリーを開く機能は出来た。 これはバッファを使って簡単な機能を作る事が出来るようになったという事でもある。結構凄いな。
とりあえずRandomThoughtsのようなディレクトリ下に大量のファイルがあるようなものでの編集作業が我慢出来る感じになった気はする。
このヒストリの実装、file_history.jsという名前だが、この辺のjsはgitで管理してないんだよなぁ。どこに置くのがいいかねぇ。
messageを実装
ミニバッファは少し大変そうなのでまずはエコー領域として表示するだけの機能を実装。
SKKの変換候補も一定以上はここに出す。
JetBrains Monoフォントにする。
Wとaで全然幅が違ってすかすかになって見苦しいので、JetBrains Monoにした。いいね、これ。
少しドッグフードをしての雑感
新しい折り畳みキーボードはプログラムをする時は記号がまだ慣れないな。 ただこれは時間の問題な気がする。
コピペとeval_regionが欲しいなぁ。次はコピペかな。 ちょっと別のファイルいじってから戻ってくると位置が失われているのもちょっと嫌なのでバッッファ切り変えは実装したいかもしれん。
ただ、どちらかというと現時点でもかなり快適に実用出来ている事に驚く。 明らかに他のエディタ+GBoardより良い。
このキーボードから手を離さなくていい感じ、久しぶりだなぁ。 最近はVSCodeとAndroidStudioなのでこういう感じじゃなかったからなぁ。 凄く気分がいい。そうそう、こういう感じだったよね。
雑に実装したヒストリーも結構いい感じだ。こういうのをJSでちょろっと書けるのはいいな。
正直実用まで持っていけるか自信が無かったが、もう十分実用的だな。 出先で長い文章を打っていたい。
ドッグフードメモ
- 選択無しでC-x C-xでクラッシュ
- BOOXでC-Spaceでマークがセットされない
2026-05-18 (月)
SKKのKeyHandlerをself-insert的に使う事でキーマップに登録
昨晩布団の中で、keyHandlerをself-insert的に全部のキーに呼ぶようにするならそこまで書き換えも大変では無いのでは?と思いつき、朝起きて試してみる。 いい感じになった。
Objectリテラルのメソッド
Rhinoへ。
促音の子音重ねのサポート
自分があさってをasatteと入力するタイプなので、このサポートをしたい。現状は「あさtて」となる。
あれ?processRomanを見るとまさに書こうと思っていた処理が書いてあるな? その後調査を続けると、どうもsetCompositionで自分の想定してない状況があるっぽく、その対応を入れたら直った!
ファイルを開く問題の短期的な解決を考える
現状ファイルを開くのはSAFのシステムのファイル選択が開くが、これがいまいちなので快適では無い。
ディレクトリ内の最近変更したファイルから選ぶ感じになっていれば我慢出来るが、システムのデフォルトが名前順で毎回変えないと日付順にならないのが不満だ。
権限的にはrootを設定さえしておけば、その下のファイルはアプリから扱えるので、ファイルを開くのでは無くディレクトリをセットする、 という風にすれば良い。ただこれはエディタ的にはめちゃわかりにくい。
もうちょっと単純に、ヒストリーがあればいいんじゃないか? 最後10ファイルを覚えておいて、その一覧を表示して、0から9までの数字で選ぶと開ける、みたいな関数を書けばいい気がする。
これをJSレイヤーで書ければいいか。
file_historyというパッケージ的なものとして書いていこう。 ファイルを開く所自体はもう現時点でもJSで書いていて、open_uriにuriは来るが、display nameが来ないな。 これはselect_fileを多値に出来れば良さそう。destructuringがあるから配列で返せばいいか。
で、ファイルに保存するのはテキストを一括で書く手抜きなwrite_fileでも用意してやれば、あとはJSON.stringifyとか使えばいいか。
で、テキストの入力を読むためのread_key的なのを作ってやればいいか。この辺はminibufferを使った同種のemacs lispの奴にそろえておく方が本当はいんだろうが、 そういう事を考え始めると時間が掛かりそうなのでまずは目先の問題を解決するとする。
という事でfile_historyを作る事に必要なものを考えてみよう。
- DONE ... select_fileにdisplay nameも返すようにする
- DONE ... write_file的なものを作る
- DONE ... read_key的なものを作る
- DONE ... get-buffer-creatとset-buffer
このくらいでいけるか?
2026-05-17 (日)
JS周りの整理
builtins.jsをオーバーライド出来るようにした。packageDirのルートにbuiltins_override.jsがある時はそちらを読むようにする。 これでしばらくローカルで開発したあとにassets下に戻す、という風に進められるようになった。
RhinocsSKKを公開、サブディレクトリのロードをサポート
SKKを直していくのはさしあたっての優先度が高いという事で、まずはうっかり消してしまっても平気なように公開しておく。README.mdとかはおいおい。
karino2/RhinocsSKK: SKK port for Rhinocs
これに合わせてskkをサブディレクトリにした都合でサブディレクトリの相対パスでのloadをサポートした。何も考えずに移動したら辞書が読めなくなったので。
最初にinit.jsをロードするように変更して、この中からrequest_load_jsでskkをロードするようにした。 requestがついているのはContextの再入が面倒だったので遅延ロードになったから。まぁ困るまではこれでいく。
JS側のKeyMap周辺を整備
SKKをKeyMap化する為にJS側のKeyMapを整備した。久しぶりにJSでprototypeとか使ってnewとかしたなぁ。 普段Electronのアプリとかは単なるオブジェクトリテラルに関数をもたせて複雑な時もその関数をクロージャにして中で複数関数を定義する程度だが、 他からも使うような物を作るならprototypeを使った書き方もまぁアリといえばアリだな。
KeyMap側は割と整理が進んだが、SKK側をキーマップに書き直すのはかなり大変な事に気づく。全部一気にやらないと動かなくなるからなぁ、これ。 ちょっと今日は撤退しておこう。
三日目 2026-05-16 (土)
SKKが意外とすぐ動きそうなので動くまではやってしまうかな、という気分になる。
SKKの変換が動く
SKKの変換をなんとなく動かす。ちょっとskkInnerまわりは扱いが微妙なので、これよりは登録用ダイアログを作りたいな。 delete-regionとかpointを実装して一応動くようになった。
改行の入力や削除などをサポート
もともといい加減だった行末、行頭への移動や削除周辺をちゃんとUnitTestを書いて直す。面倒な所はgeminiになおしてもらったりテスト増やしてもらったり。
さしあたってのToDoを考え直す
SKKが無事動いたので使い始めまでの最低ラインをもう少し考える。
- とりあえず表示だけのminibuffer(変換候補の一覧が出せないので)
- C-x C-fなどの複数ストロークのキーマップ対応
- バッファの保存
- 移動系(次の行に行けないとか)
複数キーマップ対応
jsをassets下のbuiltins.jsにちゃんと独立させて、複数ストロークのキーマップ対応を雑に書く。
簡単なファイル入出力
C-x C-fとC-x C-sをとりあえず使える程度に実装。
移動系を充実
goal_columnを実装する事でnext_lineとprevious_lineで最初の位置を保存して移動出来るようになった。
ついでにC->とC-<も実装しておく。
さらにscroll_windowを実装してC-vとM-vを実装。
C-aとC-eも実装。これでだいたい出来たかな。
SKK-JISYO.L.gz対応とkotlinで書き直し
ロードしてみたら6sec以上掛かってGUIスレッドが長すぎとダイアログがでたりするので、Kotlinでgeminiに書き直してもらった。2.6秒くらいになった。 まだ結構GCが発生しているので減らせそうだが、我慢出来ないほどじゃないので後回し。
ドッグフードを開始
試しにこのWikiを書いてみている。結構書けるな。
やはり撥音の入力が慣れないので、自分が普段入力しているやり方に対応しよう。 ただキーボードから手を離さなくていいのは快適だな。 SKK-JISYO.Lは結構漢字も多いし。候補を戻るのはxか。
ランドスケープで使ってるとカメラの穴が邪魔だな(^^;
現在のモードが分からないのはちょっと不便だな。 やはりモードラインとミニバッファが欲しいか?
skkのコードはどう公開しようかなぁ。 今はRhinocsのレポジトリとは別のgitで管理しているのだが Rhinocs側とバージョンを合わせないと使えないんだよなぁ。
まだちょっとSKKのモードが変な状態になる事があるが、 結構つかえる。SKKはchromeOS用のkeyHandlerを無理やりつなげて動かしているので、 keymapと状態がバッティングしておかしくなる場合があるんだが、これをデバッグするよりはkeymapに直した方がたぶん無難に動くよなぁ。
やはりSKK-JISYO.Lは結構実用出来るレベルだよなぁ。もう普通に使える。
二日目 2026-05-14 (木)
Window, Point, 移動を真面目に実装
forward-charを実装するにあたり、もう少しちゃんと調べた方がいいな、という事で既存エディタを調べる>エディタ調査
調査した理解を元にGridを廃止してPointから毎回計算するようにする。ついでにスクロール周りの計算もxyzzyにそろえておく。
self-insertとキーマップをなんとなく実装
キーボードから矢印キーを処理してカーソルが動かせるようになった。 さらにself-insertも実装して文字が入力出来るようになった。
SKK、ひらがな、かたかな、asciiが動く
とりあえずinsertだけで動かせるものが動く。削除も次の行も行けないのに日本語入力が動き始めるのが日本人の作ってるエディタって感じだな。
- 辞書のロード
- 候補の扱いはxyzzyのskk-modeを真似てbufferにinsertしたりで実装したい
この2つが終わればSKKがとりあえず使えるな。まぁ後者は数日は掛かるだろうが。
作り始め 2026-05-13 (水)
とりあえずバッファを画面に描く機能はなんとなく書いた。 ファイルをロードしてスクロールもなんとなく動いている。 カーソルも描いた。
Rhinoを組み込んで、RhinoからSAFでuriを取ってくるようにリクエストして、もらってきたuriをRhinoから開くように出来た。
次はキーボードの処理だな。
文字列化してJSのonKeyDownを呼ぶまでは出来た。あとはスクロールとself insertを実装すればそれっぽくはなるな。
以下は実装の最中に考えたメモ
リサイクルとenv
Androidはプロセスが殺されるので、インタープリタをずっと活かしておく、という事が出来ない。 だから殺されて再生成された時にまぁまぁの速度で動く必要がある。
emacsのようにすべてをメモリ上にずっと置いておく、という前提は難しいので、恐らく現在のバッファに対して読み込まなくてはいけないものをある程度は限定する必要はありそう。
また、再生されてから動き出すまでと、その裏でいろいろやるのは分離したい。SKKの初期化などはいかにも分離したいよなぁ。
Viewについての雑記
下の方に書いたように初期はスクロールを真面目にやろうと考えていたが、それはドッグフードまでが遠すぎてやる気が出ない。 もっと単純にしよう。
Viewはバッファのある範囲を書くが、コンソールっぽく単純化したい。 Viewのうち文字を書く領域をPaneと呼ぶ事にする。
Paneは縦横の文字のグリッドで、文字は幅が1か2のどちらかとする。フォントは等幅フォント固定。 文字の描画は文字ごとに書く。くっついたりする言語とかはサポートしないし、文字の幅を足したものが文字列の幅になるようにする。
バッファに対してある行の範囲を指定すると、その範囲でのGrid情報を計算して、rowとcolを指定すると文字が取り出せる(または前のcolが2幅なら無しが取り出せる)。 この情報はGridと呼ぶ事にするか。
PaneはGridの通りに文字を描くだけとする。スクロールとかもとりあえずは考えずに全部redrawとする。
GUIスレッドとRhinoのスレッド
とりあえずGUIスレッドでRhinoも動かす。これでは駄目なのは少し考えればわかるが、Bufferの更新とUIのレンダリングの同期がちょっと面倒なので、まずは同一スレッドで始める。
最終的にはRhinoは専用の一つのスレッドでいつも動かし、GUI側にはイベントで変更を通知し、Gridを生成する時にはBufferをロックする感じにしたい。
パッケージについて
最初にホームディレクトリを指定させる。これはSAFで。とりあえずそこにjsのスクリプト置いて、それをロードする感じでいってみたい。 本当にそれでエディタになるのかは少し自信が無い所もあるが、 カスタマイズしたい訳では無いので、既存のパッケージの関数の上書きとかはまずは出来なくてもいいだろう。 どちらかといえば途中からは実装をRhino側でやるようにしたいなぁ、という気持ちが強い。
ただActivityのリサイクルがあるので、あまり大きな初期化を最初にやる、というモデルは厳しいかもしれない。
バッファについて
バッファは行のリストで。ギャップは入れないで単純に毎回コピーする。 バッファ周りはAPIは割と真面目に作る。 StringBuilderのArrayListくらいから始めるか。
古くなった話
考えているうちに変わったものなどをとりあえず残しておく場所。
最初はStarlarkバックエンドにしようと思ってStarlarcsと呼んでいたが、今はRhinoバックエンドのRhinocsにする事にした。
SKKの実装を検討する
以前jmukがChromOS用に作ってたよなぁ、と思い調べると、そのあとhydrakecat氏がforkしている。
まぁ自分の目的ではあまり違わなさそうだが。
これをそのまま移植するならパッケージとかextensionとかいろいろ考えないといけない事が多いので、まずはそういう難しい事はせずに一つのjsファイルに必要なのを切り貼りしていく感じにするか。
SKK-JISYOは開発中はSがいいな。
SKK dictionary files gh-pages - dict
ここから落とせるか。
SKKの開発を始めるのに必要な事は以下か。
- skk.jsの場所を指定して保存するようにし、起動時に自動で読むようにする
- skk.jsと同じフォルダにあるSKK-JISYO.Sをロードする
この2つが終われば、あとはjs側の開発が始められる。
いや、一番最初は平仮名とカタカナでいいか。まずは平仮名の実装からはじめよう。
以前ちょっと調べた時にxyzzyの実装は割と簡単に実現できそうだった。
loyaltouch/Skk-Mode: xyzzy-skk-mode
この辺を参考にしつつちょっとずつ進めたい。
最初の目標
考えないといけない事は無限にあって永遠に前に進めない気がするので、一番最初の目標を決めてそれに必要な事だけをやる感じにしたい。
やりたいのはこのWikiなどを書きたい。それに必要最低限のものから始めたい。
- SAFでのファイルの読み書き
- 日本語入力
- SKK
- ダイアログでEditText
- キーマップと基本的な入力
- カット、コピー、ヤンク
日本語入力は必須なので、最初から半角-全角の扱いはしたい。逆にこの枠組みに収まらない言語はやらない。
SKKはちゃんとやると結構いろいろな機能が必要になってしまうので、とりあえず複数windowが無くても出来るような範囲だけをやりたい。 ミニバッファ絡みの機能も最初はやらない。
その代わりEditTextで普通にタッチ入力する逃げ道は作っておきたい。
ウィンドウのsplitは無しでもまずはいけるか?ミニバッファとウィンドウのsplitがなければだいぶ話は楽そうだが。 switch-bufferは必要だろうが、最初の目標としては無しの状態でとりあえず動く所までを目指すか。
最初の最初の目標はSKKの実装を開始するまでの最低限
SKKを必要最低限に実装するのは結構大変ではある。 これが最初の目標というのは遠すぎる。
そして作り始めないと何が必要かは完全には分からないので、最初に作り始める所を最初の最初の目標とするか。
SKKを作り始めるのが最初の最初の目標とすると、必要なのはなんだろう?
- SAFでのファイルの読み書き
- Rhinoのキーマップ経由のローマ字の入力
- Rhinoによるjsのロード
モードとかを最初に整備するのは避けたいので、単純にSAFでskk.jsをロードしてしまって良いとは思っている。 このくらいが動けばjsの作業を開始出来るか。
ここまでを最初の最初の目標とするか。
SKKを書き始めるのにとりあえず必要なのは以下か?
- Rhinoからのバッファの操作
- Rhinoからの辞書のロード
- Rhinoからのread-key的な処理
この辺を揃え始めるのが最初の最初の目標かな。ドッグフードはまだまだだいぶ先だが。
Viewについての雑記(初期、やっぱり変える)
一行一TextViewで、RecyclerViewを使いたいが、厳しいかなぁ。 内部的には対応する行に合わせたグリフ情報を構成して、それを書く。 各TextViewは前に書いたグリフ情報はキャッシュし、、、ってそこまでやるならTextViewよりも自分で書く方がいいかなぁ。
View側と内部の両方を一気につくると永遠に完成しないのでViewは手抜きで始めたい気もするな。 そうすると一番単純にはスクロールとかは無視して、画面に表示している範囲の文字をdrawするだけの方が楽か。 まぁ最初はそこからか。
そう考えるならZipSourceCodeReadingと同じ感じでいいか。 ZipSourceCodeReadingはスクロールがいまいちだったので、将来的には各行を表示するViewを作ってRecyclerViewに任せたい気もするが、 キーボード中心と割り切れば別にスクロールがいまいちでも我慢出来るかもしれない。 なんにせよ最初は単に配列を画面に表示するだけの方がいいな。
とりあえず、内部で画面に表示する文字の一覧を作る。これはグリフの配列とする。 Viewはそれを表示するだけで、スクロールとかは一切サポートしない。 こんな感じで始めるか。 グリフを作る所まではどうせ一緒なので、そこから先は真面目に書きたくなったら書けばいいだろう。