Node.jsでターミナルで動くテキスト編集可能なアプリケーションを作る
こんなの作ってます
何ができるのか?
- BoostNote(v1)のデータの読み書き
どうやって作ったか?
使うもの
- TypeScript
- blessed
- Node.jsからターミナルの表示を色々いじれるライブラリ
- MobX
- アプリケーションのベース部分
- TextBuffer
- テキストファイルへのアクセス補助
構成図
使うもの
- TypeScript
- blessed
- Node.jsからターミナルの表示を色々いじれるライブラリ
- MobX
- アプリケーションのベース部分
- TextBuffer
- テキストファイルへのアクセス補助
構成図
blessedの使い方
blessedは今風の宣言的UIライブラリではないので、結構泥臭い感じになります。(一応Reactでラップしたライブラリもありますが、今回は素のblessedを使いました。)
このアプリの場合は、画面内の各要素ごとに自前のコンポーネントを作成し、その中でblessedのパーツを描画する感じにしました。
import ...
type NoteListOptions = SetRequired<blessed.Widgets.ListOptions<any>, 'parent'>;
const kDefaultNoteListOption: Readonly<Partial<NoteListOptions>> = Object.freeze({
keys: true,
mouse: false,
scrollbar: {
ch: ' ',
track: {
bg: 'cyan',
},
style: {
inverse: true,
},
},
style: {
item: {
hover: {
bg: 'blue',
},
},
selected: {
bg: 'blue',
bold: true,
},
},
});
interface NoteList extends LoggableMixin, ReactableMixin {}
class NoteList {
private noteList: blessed.Widgets.ListElement;
constructor(options: NoteListOptions) {
// blessedのパーツはコンポーネントで保持する
this.noteList = blessed.list({
...kDefaultNoteListOption,
...options,
});
// コンポーネントのインスタンスメソッドを、パーツにイベントリスナーとして設定する
this.noteList.key(['up'], this.onUpKeyPressed);
this.noteList.key(['down'], this.onDownKeyPressed);
this.noteList.key(['f'], this.onFolderKeyPressed);
this.noteList.key(['c'], this.onCKeyPressed);
this.noteList.on('select', this.onSelect);
this.makeReactable();
}
focus() {
this.noteList.focus();
}
@boundMethod
onUpKeyPressed() {
if (0 < appStore.currentShowDocumentIndex) {
appStore.setCurrentShowDocumentIndex(appStore.currentShowDocumentIndex - 1);
}
}
@boundMethod
onDownKeyPressed() {
if (appStore.currentShowDocumentIndex < appStore.currentFolderDocuments.length - 1) {
appStore.setCurrentShowDocumentIndex(appStore.currentShowDocumentIndex + 1);
}
}
...
}
...
MobXの使い方
MobXは基本的にはReactと組み合わせて使うのが王道ですが、単体でも使えるということを自分なりにやってみたかったので使いました。
実装としてはシンプルに、MobXのobservableをクラスとして作り、そこにいろんな情報を入れて、先程の自前コンポーネントからsubscribeする、という感じです。
MobXのobservableの定義
class AppStore {
...
/**
* Folders
*/
@observable
private _folders: Folder[] = [];
@computed
get folders() {
return this._folders;
}
@actionAsync
async loadFolders() {
this._folders = JSON.parse(await task(fs.readFile(config.folderFilePath, { encoding: 'utf8' }))).folders;
}
...
}
observableを使うコンポーネント
class FolderList {
private folderList: blessed.Widgets.ListElement;
constructor(options: FolderListOptions) {
this.folderList = blessed.list({
...
},
parent: options.parent,
});
this.folderList.on('select', this.onSelect);
this.makeReactable();
}
...
// reactionMethodは、自分で作ったデコレータ
// this.makeReactableを呼び出すと、MobXのreactionとして動くようになります
@reactionMethod(() => appStore.isInitialized)
reloadItems() {
this.folderList.setItems(appStore.folders.map((folder) => folder.name));
this.folderList.select(appStore.currentFolderIndex);
this.folderList.screen.render();
}
}
正直Reactみたいなわかりやすいデータフローでもなく、subscribeがいろんなところに散らばって見づらい感じはあるんですが、一応実装は出来ます。
TextBufferの使い方
TextBufferとはAtomで使われている、テキストファイルへのアクセスを簡単にするためのライブラリです。
yabaiでは、下記のような使い方をしています。
テキストファイルへのアクセスは全てTextBufferに任せ、テキストエディタコンポーネントはあくまで表示の処理に集中する、という使い方です。ちなみにこの実装のやり方は、blessedベースのテキストエディタである、slapを参考にして作りました。
テキストファイルの読み込み、書き込みなどの操作を全てTextBufferに任せられるので非常に楽です。
Tips
blessedが更新されていない
blessedは既に更新が止まっているようで(でも動く)、いろんな人がforkしてオレオレblessedを作っているようです。このアプリケーションでは、その中でももっとも更新の頻度が高そうなneo-blessedを選びました。
※さらに一部修正したい部分があったので、モノレポのパッケージの一つとしてリポジトリに入れてしまってます。
CoffeeScriptがWebpackでバンドルできない
BoostNoteのデータファイルは、csonベースなのでcsonを依存に含める必要があります。これが非常に悩みポイントでした。csonを依存に入れるということはcoffeescriptを依存に入れる、ということなのですが、coffeescriptはrequireが独自のものを使ってたりと色々癖があり非常に大変でした。
https://github.com/mk2/cson-parser
https://github.com/mk2/coffeescript
ここにWebpackでバンドル可能なcson-parser/coffesscriptを置いておきます。ちょっと手が滑ってコミット履歴など全て消し飛んでしまったのですが、修正点としては設定部分だけです。
Author And Source
この問題について(Node.jsでターミナルで動くテキスト編集可能なアプリケーションを作る), 我々は、より多くの情報をここで見つけました https://qiita.com/mk2/items/05d274c384c8b2774fe3著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .