Open14

Emacs: textlintを導入してみた(大変だった)

kabeyakabeya

macOSでは普段XcodeとEmacsを使っています。
SwiftのコードはXcodeで直に打っています。
Emacsはそれ以外のものの編集に使う感じですかね。

ちょっとtextlintを使おうと思い立って、色々調べてやっては止まって…ってなっていましたので、ちょっと思い出しながらメモしておきたいと思います。
たぶんどこかでやり直す日が来て、同じことで苦労すると思いますので。

kabeyakabeya

まずちょっと悩んだのが、textlintをどこにインストールするかですね。
グローバルに(=/usr/localに)インストールする、という人も居れば、--save-devでローカルにインストールする、という人も居て。

とりあえず私はローカルにインストールしました。
具体的にはzenn用のディレクトリを作って、そこをGitHubと連携したうえで、そこにインストールしました。
GitHubと連携しているディレクトリが例えば~/zennだったら、

$ cd ~/zenn
$ npm install --save-dev textlint textlint-rule-preset-ja-technical-writing  textlint-rule-spellcheck-tech-word
$ npx textlint --init

ってやる感じですね。textlint以外のルールも一緒にインストールします。上に書いたもの以外にも色々あるので一緒にインストールしたら良いと思います。
npm init -yは、GitHub連携の際に作られたファイルとかがあるので不要です。
GitHub連携してない場合はnpm init -yをしてください。

kabeyakabeya

textlint自体は動くようになったのですが、色々見るとVSCodeが良いって話なんですよね。
早速ダウンロードしてみました。

ですが、なんでしょう。コマンド+Sで保存ができない。というかメニューから選んでも保存ができない。
「閉じる」ってやると「保存しますか?」って聞かれて、それで「保存」を選んでやっと保存できる。
調べたけど、これは結局解決しませんでした。

自動保存を有効にすると、自動保存はしてくれるんですね。
そうするともう「保存」は選ばなくてもいいんです。ただ、ちょっと、というかかなり気持ち悪い。

いちおうVSCodeにvscode-textlintを入れて、チェックされるところまでは確認しました。
ただ自動保存のせいなのか何なのか、何も操作していない間にも繰り返しチェックが走っているようでした。
結局、これも解決はできませんでした。

環境のせいなんでしょうか。
というわけで、いったんVSCodeは諦めました。

kabeyakabeya

といって、コマンドラインから都度textlintを実行するのはやはり手間だと思いましたので、他の方法を調べていると、Emacs+flycheckというパッケージでtextlintを実行してバッファ内に表示できる、ということなんですね。

Emacs自体は、いつインストールしたのか分かりませんが28.3が入っていました。
(あとで、最新版の29.3に入れ替えました)

それでまずflycheckをインストールしました。
これはflycheckの公式ページに書いてあるインストール手順に従ってできました。
Syntax Checking Toolsの部分は、今回はtextlintだけ動けば良く、それはもう入れたのでスキップしました。

kabeyakabeya

flycheckの設定については以下の2ページを参考にしました。

https://www.mhatta.org/wp/2022/12/30/how-to-proofread-japanese-using-textlint-on-emacs/

https://mako-note.com/ja/textlint-emacs/

textlintというコマンドはなくnpx textlintで実行するのですが、せっかくなのでtextlintというコマンドを作りました。

#! /bin/sh
npx textlint $@

これを~/binにでも置いてchmod 755したらいいんでしょ、と思ったら、~/binもなくてパスも通っていませんでした。~/.zprofileにexport PATH="$HOME/bin:$PATH"を追加しました。

ちなみに後で分かったのですが、最初のページにある"-format"は、正しくは"--format"でした。
確かにEmacs内でエラーメッセージが出ていたのですが見落としていて苦労しました…
結局コマンド側に引数を付けることにしました。

#! /bin/sh
npx textlint --format unix $@
kabeyakabeya

インラインで表示できるって言うのにできないなと思っていたら、追加でパッケージを入れる必要があると言うことでした。

https://mako-note.com/ja/python-emacs-ide/#flycheck

flycheck-inline(と、モードラインにチェッカの状態を表示するflycheck-color-mode-lineもついでに)というパッケージを入れるんですね。

ただ、package.elでlist-packagesをしたのですがその2つがないんです。
上の記事ではパッケージ管理にleaf.elを使っているっていうことなんで、たまたまpackage.elのほうにはないということなのかなと思いました。

GitHubのflycheckのところからflycheck-inline.el、flycheck-color-mode-line.elを取ってきて、自分の.emacs.dにおいて、.emacs(最近の人ならinit.el)に

(load-file "~/.emacs.d/flycheck-inine.el" t)
(load-file "~/.emacs.d/flycheck-color-mode-line.el" t)

(with-eval-after-load 'flycheck
 (add-hook 'flycheck-mode-hook #'flycheck-inline-mode))
(with-eval-after-load 'flycheck
 (add-hook 'flycheck-mode-hook #'flycheck-color-mode-line-mode))

を追記しました。

kabeyakabeya

で、なかなかインラインで表示されないんです。
何が悪いのかわからなかったので色々試しました。
npx textlint --format unixは、ファイル名をフルパスで出力するんですね。これが悪いのかと思って、ファイル名だけにするような処理に変更しました。Perlで。

#! /usr/bin/perl

open(IF, "npx textlint --format unix @ARGV|") || die;
while (<IF>) {
    if (/^([^:]*\/)[^:]*:/) {
	$_ = substr($_, length($1));
    }
    print $_;
}
close(IF);

結局これによるせいなのかどうなのか分かりませんが、この時点で指摘内容がインラインで表示されるようになりました。

kabeyakabeya

で、Emacsでマークダウンの記述をチェックしていると、フォントを変えよるんですね。勝手に。というかフォーマットしてくれるというか。

そのフォーマットが気に入らないんです。コードとか等幅なんで普段使っているフォントそのままでいいのにわざわざ変なフォントに変えたり。ヘッダのフォントをボールドにするせいで、等幅がずれたり。
原則デフォルトのフォントを使うようにして、色だけ変えるようにしました。

(set-face-attribute 'markdown-code-face nil :inherit 'default :foreground "PaleVioletRed2")
(set-face-attribute 'markdown-inline-code-face nil :inherit 'default :foreground "PaleVioletRed2")
(set-face-attribute 'markdown-header-face-1 nil :inherit 'default :foreground "PaleGreen1")
(set-face-attribute 'markdown-header-face-2 nil :inherit 'default :foreground "PaleGreen1")
(set-face-attribute 'markdown-header-face-3 nil :inherit 'default :foreground "PaleGreen1")
(set-face-attribute 'markdown-header-face-4 nil :inherit 'default :foreground "PaleGreen1")
(set-face-attribute 'markdown-list-face nil :inherit 'default :foreground "cyan2")
(set-face-attribute 'markdown-markup-face nil :inherit 'default :foreground "LightSteelBlue1")
(set-face-attribute 'markdown-language-keyword-face nil :inherit 'default :foreground "khaki1")
kabeyakabeya

そのうえでよくよく見ると、漢字の一部がなんかおかしい。
中国文字になっているんですね。

https://qiita.com/j8takagi/items/01aecdd28f87cdd3cd2c

を参考に、漢字のところのフォントを調べるとPingFang SCというフォントになっています。

で、これも以下を参考に、.emacsに(set-language-environment "Japanese")を追加したら直りました。

https://hylom.net/2020/11/23/emacs-settings-to-use-japanese-font/

kabeyakabeya

あと、デフォルトのフォントというのがだんだん気に入らなくなってきて、でもフォントいじるの面倒だな、という感じだったのですが。

https://aoe-tk.hatenablog.com/entry/2019/02/25/000154

を見ると、あら簡単。
フォントパネルで選んで、ツールバーからSave Optionsってやると自動で.emacsに設定を書き込んでくれるんですね。

ちなみに、いつの間にかフォントパネル表示させてフォントを変えても表示が変わらなくなってしまいました。
いまのところとりあえず、.emacsに書かれた設定を手動で書き換えることで対処しています。

今は「Ricty Diminished」にしています。

kabeyakabeya

ローカルに保存した画像をマークダウン内に貼れないかなと思って調べました。

https://zenn.dev/eguchi244_dev/articles/github-zenn-img-mgmt-20230511

GitHubに入れて連携できるのは分かったんですが、VS Codeでは画像をコピペすると画像ファイルが生成されてリンクがマークダウン内にペーストされるっていうんです。

それはEmacsでもやらなあかんと思いまして調べました。

https://qiita.com/hibitomo/items/7e955ba5d951398f0cc1

https://qiita.com/Ladicle/items/e7fcacac931a27578aa4

で、pngpasteをインストールして、.emacsに以下を書きました。

(with-eval-after-load "markdown-mode"
  (defun markdown-insert-clipboard-image ()
    "Generate png file from a clipboard image and insert a link to current buffer."
    (interactive)
    ;; zenn用の以下のディレクトリ構成内に、編集中の.mdがあるとして処理。root/imagesに以下のような感じでファイルを入れたい
    ;; +- root
    ;;    +- articles
    ;;    |  +- article1.md
    ;;    |  +- article2.md
    ;;    +- books
    ;;    |  +- book1
    ;;    |     +- section1.md
    ;;    |     +- section2.md
    ;;    +- images
    ;;       +- articles
    ;;       |  +- article1
    ;;       |  |  +- yyyymmdd-hhmmss.png
    ;;       |  |  +- yyyymmdd-hhmmss.png
    ;;       |  +- article2
    ;;       +- books
    ;;          +- book1
    ;;             +- section1
    ;;                +- yyyymmdd-hhmmss.png
    ;;                +- yyyymmdd-hhmmss.png
    (let* ((md-file-path (buffer-file-name))
	   (parent-dir-path (file-name-parent-directory md-file-path))
	   (parent-dir-name (file-name-nondirectory (directory-file-name parent-dir-path)))
	   (md-file-title (file-name-sans-extension (file-name-nondirectory md-file-path)))
	   (content-root-path (if (equal parent-dir-name "articles") (file-name-parent-directory parent-dir-path)
				(file-name-parent-directory (file-name-parent-directory parent-dir-path))))
	   (content-subdir-path (if (equal parent-dir-name "articles") (file-name-concat "articles" md-file-title)
				  (file-name-concat "books" parent-dir-name md-file-title)))
	   (image-dir-path (file-name-concat content-root-path "images" content-subdir-path))
	   (file-name (format-time-string "%Y%m%d-%H%M%S.png"))
	   (file-path (file-name-concat image-dir-path file-name))
	   (file-relpath (file-name-concat "/images" content-subdir-path file-name))
	   )
      (message "Create File %s..." file-path)
      (unless (file-exists-p image-dir-path)
        (make-directory image-dir-path t))
      (shell-command (concat "pngpaste " file-path))
      (if (file-exists-p file-path)
          (insert (concat "![](" file-relpath ")")))
    )
  )
  (define-key markdown-mode-map (kbd "C-M-y") 'markdown-insert-clipboard-image)
)

elispの良い感じの書き方がよく分からないせいでくどいのですが、良い感じに動きました。

kabeyakabeya

それで、まあ(書きかけですが)本を公開しました。

https://zenn.dev/kabeya/books/swift-pointer-guide

なんていうか。
本当は全部書いてから公開しようと思ってましたが。
表紙を作った段階で、この表紙を早く公開したくなってしまい、
途中なのに公開してしまいました!
(ヒント:3話「敵の補給艦を叩け!」)

良くない傾向かも知れませんし、そうでないかも知れません。

kabeyakabeya

Emacs + textlint + zenn-cliで書き始めていたのですが…

Microsoft Wordに戻ってしまいました!

なんていうか、章を分けずに管理できたり、全体のアウトラインが表示できたり。
Pagesも試してはみたんですけども、かゆいところに手が届かないというか。

WordはWordで、かゆくないところまでも掻いてしまって、ちょっとうざいんですけども。

でも、ちょっとした記事を書くのは圧倒的にマークダウンが楽ですね。

最初に構成がかたまっていて、書く内容もほぼ頭の中にあって、書き下すだけ、というならマークダウンでいいんでしょうね。今回は、たいしてかたまってないうちに書き始めちゃったので、ぐちゃぐちゃなんですよね。

kabeyakabeya

自分は、zenn記事用のフォルダをOneDrive連携している場所に作っています。そこにzenn-cliも一緒に入っています。
で、気付かないうちに記事のファイルや画像、それにzenn-cliのモジュール類の一部がローカルからOneDriveのサーバに追い出されました。
(追い出された=サーバにバックアップされたうえでローカルから削除)

本来は、アクセスすれば元に戻る、ということなんですが戻りません。
結果的にプレビューできなくなってしまいました。

Finderで、zenn記事用のフォルダを選択して、右クリック→クイックアクション→このデバイス上で常に保持するってやって20〜30分待ったら、ローカルに戻ってきてプレビューできるようになりました。

危険ですね。
zenn-cliはグローバル(正確にはOneDriveの連携対象外の場所)にインストールしたほうがいい気がします。