textlintとFlycheckによるEmacs上でのテキスト校正

2021年12月13日 2024年1月12日

この記事はEmacs Advent Calendar 2021の13日目の記事です。

こんにちは。

突然ですが、Emacsでは、人工言語(Python, Rust等)でのコーディングだけでなく、メモ、ブログ、原稿書き等の自然言語(日本語や英語等)での入力も多数発生します。 自分用のメモだけであれば自分が理解できれば良いですが、他の人にも読んでもらうならば冗長な表現や誤字・脱字をなるべく避けたいものです。

そこで今回は、textlintとEmacsのFlycheckを用いて、リアルタイムのテキスト校正方法を紹介します。 この記事が少しでも読まれた方の参考になると嬉しいです。

textlint

textlintとは、テキストファイルを特定のルールに従ってチェックや修正してくれるツールです。@azu_reさんという方が作られています。 ルールには色々あり、技術文書向け、誤字・脱字、英語向け、日本語向けのルール等、多数公開されています。

まず、このツールを用いて、テキスト校正をできるようにします。

textlintのインストール

textlintのインストールにはnodejsが必要になりますので、nodejsをインストールしておきます。 nodejsのインストール方法は色々な記事がありますので、そちらを参照いただければと思います。 私もM1 Macでのnodejsのインストール記事を書いていますので、よろしければ参考にしてください。

nodejsがインストールされていれば、以下コマンドでtextlintをインストールします。

Terminal
npm install -g textlint

公式のインストールではlocalでのインストールを推奨していますが、私は色々な箇所で利用するためglobalにインストールしています。

テキスト校正ルールのインストール

textlintのみではテキスト校正ができませんので、利用したいテキスト校正ルールをインストールします。 テキスト校正ルールは色々な種類が公開されていますので、自分が使いたいルールを選びます。

参考までに私が使っているルールを以下に記載します。

言語ルール名概要
日本語textlint-rule-preset-ja-technical-writing技術文書向けのルール
日本語textlint-rule-preset-ja-spacingスペースに関するルール
英語textlint-rule-write-goodwrite-goodに基づくルール
英語textlint-rule-en-capitalization大文字小文字に関するルール
英語textlint-rule-alexALEXに基づくルール
英語textlint-rule-common-misspellingsWikipediaに基づくスペルミスのルール

ルールも以下のようにnpmでglobalインストールします。

Terminal
npm install -g textlint-rule-preset-ja-technical-writing

org-modeへの対応

Emacsにはorg-modeがあります。 org-modeでのフォーマット(.orgファイル)でもtextlintを使いたいので、以下コマンドでorg-mode用のpluginをインストールします。

Terminal
npm install -g textlint-plugin-org

なお、textlint-plugin-orgの依存関係になっているdate-fnsのバージョンが3以上だとエラーになるので、textlint-plugin-orgのインストール後にdate-fnsのバージョン2.30.0にしています。

言語によるルール選択とプラグイン設定

textlintのルールやプラグインは、コマンドの引数で指定するか、Configファイルを指定して、適用します。

日本語/英語で適用したいルールが異なりますので、日本語用と英語用のConfigファイルを作成します。 保存ディレクトリは各自で自由に設定してださい(私は~/.config/textlintにしています)。

  • 日本語用Configファイル
    ~/.config/textlint/textlintrc_ja.json
    {
      "rules": {
        "preset-ja-technical-writing": {
          "max-kanji-continuous-len": {
           "max": 10
          }
        },
        "preset-ja-spacing": {
          "ja-nakaguro-or-halfwidth-space-between-katakana": true,
          "ja-no-space-around-parentheses": true,
          "ja-no-space-between-full-width": true,
          "ja-space-between-half-and-full-width": {
            "space": ["alphabets"]
          },
          "ja-space-after-exclamation": false,
          "ja-space-after-question": false,
          "ja-space-around-code": false,
          "ja-space-around-link": false
        }
      }
    }
  • 英語用Configファイル
    ~/.config/textlint/textlintrc_en.json
    {
      "rules": {
        "alex": true,
        "common-misspellings": true,
        "write-good": true,
        "en-capitalization": true
      }
    }

次に、自動で以下の2点を実現する必要がありますので、私は以下のshellscript(textlint.sh)を作成しています。

  • 言語別に適用するConfigファイルを選択
    • text内にひらがなが含まれていれば日本語、含まれていなければ英語と判定
  • orgファイルにはorg用オプションを指定
    • 拡張子がorgであれば、org用オプションを追加

なお、このtextlint.shはEmacsから呼び出しますので、EmacsからPathが通っている箇所に保存してください。私は~/local/bin/textlint.shとして保存しています。

~/.local/bin/textlint.sh
#!/usr/bin/env bash

set -eu
set -o pipefail

FILENAME="$1"
EXTENSION="${FILENAME##*.}"

determine_textlintrc() {
    if grep -q "[ぁ-ん]" "$1"; then
        echo "${HOME}/.config/textlint/textlintrc/textlintrc_ja.json"
    else
        echo "${HOME}/.config/textlint/textlintrc/textlintrc_en.json"
    fi
}

TEXTLINTRC=$(determine_textlintrc "$FILENAME")

ARGS=(--format unix --config "$TEXTLINTRC" "$FILENAME")

if [ "$EXTENSION" = "org" ]; then
    ARGS+=(--plugin org)
fi

textlint "${ARGS[@]}"

最後に、textlint.shに実行権限を付与します。

Terminal
chmod +x ~/.local/bin/textlint.sh

textlint.shの動作確認

では、実際に動作確認をしてみます。まず、test用のファイルを作成します。

~/test_ja.txt
今日は、天気が良さそうに思います。
~/test_en.txt
i talk abotu English.

上記で作成したtest用ファイルを引数にしてtextlint.shを実行すると、ちゃんと言語ごとに警告が表示されていることが確認できます。 ここには結果を記載していませんが、orgファイルでも適切に利用できます。

% textlint.sh test_ja.txt

test_ja.txt:1:13: 弱い表現: "思います" が使われています。 [Error/ja-technical-writing/ja-no-weak-phrase]
% textlint.sh test_en.txt

test_en.txt:1:1: Paragraph: Follow the standard capitalization rules for American English.
See https://owl.english.purdue.edu/owl/resource/592/01/ [Error/en-capitalization]
test_en.txt:1:8: This is a commonly misspelled word. Correct it to about [Error/common-misspellings]

以上で、textlintの設定が完了です。

Flycheck

次にEmacsでのリアルタイム実行です。 Emacsでは、構文チェッカーであるFlycheckを用いてリアルタイムのtextlintを実現します。

Flycheckのインストールと初期設定

まず、Flycheckパッケージをインストールします。 Flycheckのインストール方法は色々な記事がありますので、そちらを参照いただければと思います。 私もFlycheckのインストールと初期設定記事を書いていますので、よろしければ参考にしてください。

~/.emacs/init.el
;; flycheckのインストールと初期設定
(leaf flycheck
  :ensure t
  :hook ((text-mode-hook markdown-mode-hook gfm-mode-hook org-mode-hook) . flycheck-mode)
  :custom ((flycheck-display-errors-delay . 0.3)
           (flycheck-indication-mode . 'left-margin))
  :config
  (add-hook 'flycheck-mode-hook #'flycheck-set-indication-mode)
  (leaf flycheck-inline
    :ensure t
    :hook (flycheck-mode-hook . flycheck-inline-mode))
  )

私はPackage管理のパッケージとしてleaf.elを使用しており、leaf.elの記法で設定しています。

Flycheckの設定

Flycheckでtextlintを使うためには、textlint向けのチェッカーを作成する必要があります。 こちらを参考に以下のようにチェッカーを作成しました。

~/.emacs/init.el
;; textlint用のチェッカー
(flycheck-define-checker textlint
  "A linter for text."
  :command ("textlint.sh" source)
  :error-patterns
  ((warning line-start (file-name) ":" line ":" column ": "
            (id (one-or-more (not (any " "))))
            (message (one-or-more not-newline)
                     (zero-or-more "\n" (any " ") (one-or-more not-newline)))
            line-end))
  :modes (text-mode markdown-mode gfm-mode org-mode))

上記では、textlint用チェッカーをtextとmarkdown、orgファイルで起動するようにしています。

また、:commandで先ほど作成したtextlint.shを指定しています。 私はtextlint.shをEmacsからpathが通っている箇所に保存しているので、ファイル名を直接指定します。 pathを通していない場合は、pathを通すかtextlint.shを絶対pathで指定してみてください。

動作確認

ではEmacsで動作確認をします。 ちょうど本記事もEmacsで書いていますので、その画面を以下に示します。

初期画面

しっかり、適切でない表現が指摘されていることが分かります。これで完了です。

終わりに

ここまで、読んでいただき、ありがとうございます。 これでリアルタイムでのテキスト校正環境ができあがりました。

もちろん、指摘だけでなく自動修正も可能ですが、どのように修正されるか分かりませんし、指摘による気付きで自身の執筆能力が向上するはずです(たぶん)。

この記事で少しでもEmacsユーザが増え、Emacs界?の盛り上がりに貢献できると嬉しいです。

すべてのEmacsユーザに幸あれ!

Posted by mako
関連記事
コメント
...