Pulsar上でテキスト生成できるようにした

# はじめに
Pulsarテキストエディタ上でLLMによるテキスト生成を行えるようにパッケージを開発しました。

https://web.pulsar-edit.dev/packages/assisted-writing

これは、ファイル先頭からカーソル位置までをプロンプトとしてLLMに投げ、生成結果をカーソル位置に順次挿入する、という単純な動作をするものです。
![screenshot.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2650925/33ff3aa7-74e0-3548-4a8a-c26e37b28692.gif)
[text-generation-webui](https://github.com/oobabooga/text-generation-webui)のnotebookと似たようなことをエディタ上で実現する、といった感じ。
バックエンドとしては[llama.cpp](https://github.com/ggerganov/llama.cpp)か[text-generation-webui](https://github.com/oobabooga/text-generation-webui)で動作するローカルLLM、もしくは[Google AI Studio](https://ai.google.dev/aistudio)の[Gemini API](https://ai.google.dev/api?lang=web)を使えるようにしてあります。

この記事ではPulsarパッケージ開発の大まかな流れや便利だった情報源を軽くまとめます。

# 前提知識
* Pulsar: 旧Atom。[GitHubによる開発が終了](https://github.blog/news-insights/product-news/sunsetting-atom/)した後、コミュニティがメンテしているもの。
* パッケージ: Pulsar用の拡張機能。Pulsar自体がChromiumの上で動いているので、ごく普通のJavaScriptで書けば動く。
    * 要はChromiumなので、おなじみDeveloper Toolsが動作する。JavaScriptデバッガも使える。
    * wasmも動作する。今回のパッケージではOpenAI系のトークナイザtiktokenのwasm実装が動いている。
* テストフレームワークは[Jasmine](https://jasmine.github.io/)。
* ppm: pulsar package manager。旧apm。Atom時代のドキュメントでapmと書いてある部分はこれで置き換える。

# 情報源

* [Pulsar Launch Manual](https://pulsar-edit.dev/docs/launch-manual/): 一次情報。旧Atom Flight Manual
    * [Hacking on the Core](https://pulsar-edit.dev/docs/launch-manual/sections/core-hacking/#hacking-on-the-core)の節からがパッケージ開発関係のドキュメント
* [旧Atom Flight Manual](https://flight-manual.atom-editor.cc/api/v1.63.1/AtomEnvironment/)ミラー?のAPIリファレンス: Pulasr Launch ManualにはAPIリファレンスが移植されていないのでこちらを参照すると便利。
* [pulsar-edit.github.ioレポジトリ launch manualソース](https://github.com/pulsar-edit/pulsar-edit.github.io/tree/main/docs/docs/launch-manual/sections/core-hacking/sections): Launch Manualのmarkdownソース。[NotebookLM](https://notebooklm.google/)にソースとして投入するのに便利。

# 開発の流れ
## Package Generatorを実行
コマンドパレットから `Package Generator` を実行。必要項目を入力すると空のパッケージが作成されます。

## 設定項目の定義
cf. [Config](https://flight-manual.atom-editor.cc/api/v1.63.1/Config/)
package mainにjson schemaで定義していきます。
```javascript
export default {
  ...
  config: config,
  ...
```

```javascript
const config = {
  local: {
    title: 'Local LLM',
    type: 'object',
    properties: {
      enable: {
        title: 'Enable',
        type: 'boolean',
        default: true,
      },
    ...
```

設定値の型はstring, integer, number, booleanなどがあります。enumキーを定義すればドロップダウンでの設定項目も作れます。

設定値は以下のように、 `atom.config.get()` を用いて取り出します。
```javascript
atom.config.get('assisted-writing.local.enable')
```

## コマンドの定義
コマンドパレットから起動できるコマンドはpackage mainの `activate(state)` 中にて以下のように定義します。
```javascript
export default {
  ...
  activate(state) {
    ...
    this.subscriptions.add(
      atom.commands.add('atom-text-editor', {
        'assisted-writing:run': () => this.run(),
        'assisted-writing:abort': () => this.abort(),
      ...
```
キー文字列がコマンド名となり、実行されると値の関数が実行されます。

## ホットキーの定義
keymaps/パッケージ名.jsonでホットキーを定義できます。
```json
{
  "atom-text-editor": {
    "ctrl-alt-enter": "assisted-writing:run"
  },
  "atom-text-editor.assisted-writing-running": {
    "escape": "assisted-writing:abort"
  }
}
```
この場合、
* atom-text-editorにフォーカスがある場合、CTRL+ALT+ENTERでassisted-writing:runコマンドを実行する
* atom-text-editorに"assisted-writing-running"クラスが付いているものにフォーカスがある場合、ESCでassisted-writing:abortコマンドを実行する

という2つのホットキーが定義されています。

## テスト
コマンドパレットから Window: Run Package Specsを実行するか、コマンドラインで `ppm test` を実行するとテストケースが実行されます。前述の通りテストフレームワークとしてJasmineがデフォルトで入っています。
GitHub Actions等でCIを組む場合、 `ppm test` の実行にはlibnotify4とxvfbが要るので注意してください。
```yaml
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4
      - name: Setup Pulsar
        run: |
          sudo apt install -y libnotify4 xvfb
          wget https://github.com/pulsar-edit/pulsar/releases/download/v1.119.0/Linux.pulsar_1.119.0_amd64.deb
          sudo dpkg -i Linux.pulsar_1.119.0_amd64.deb
      - name: Install dependencies
        run: ppm i
      - name: Run tests
        run: xvfb-run -a ppm test
```

## 公開
`pulsar -p publish (major, minor or patch)` で公開します。この際、以下のものが必要です。
* GitHub Personal access token
    * fine-grained personal access tokenの場合、以下の設定にする
        * Only select repositories: パッケージのレポジトリを指定
        * Repository permissions
            * Contents: read and write
* Pulsar API token
    * https://web.pulsar-edit.dev/login からGitHubアカウントでログインすると得られる

publishを実行するとバージョンがインクリメントされたコミットが作成され、タグが作成されてpublishされます。