パラグラフ・ライティングを支援するツール

成果物

エディタでパラグラフ・ライティングを支援する機能を作成してみた。

利用風景を動画で紹介した。

なお、これ以外に、画面をScrivener風に設定する方法や、文書の章・節単位で字数を数える方法、文書校正をする方法なども過去には紹介しているので、これらをあわせると文書作成はかなり便利になる。

やりたいこと

パラグラフ・ライティングをする際に、トピックセンテンスの並びを見ながら執筆したい。パラグラフ・ライティングは意味のまとまりごとに、パラグラフ(段落)を作っていく。各パラグラフの最初にはそのパラグラフの内容をまとめたトピックセンテンスがくる。だから、トピックセンテンスだけ読んでいけばその文書にかかれていることがだいたい把握できる。

執筆が進み各パラグラフに情報が追加されていくと、前後のトピックセンテンスが見えなくなってくる。最初はトピックセンテンスが並んでいるだけなので、文書全体を見渡すことも簡単だ。だけど、各パラグラフの内容が充実してくると、一画面に4-5個のパラグラフしか表示できなくなる。そうすると、当然だけど文書全体の見通しが悪くなる。前後で書くべきことが分からなくなると、今書いているパラグラフに書くべきこともあいまいになってしまうので、つい不要なことも書いてしまう。

文章のトピックセンテンスのみ表示することができれば、文章の全体像を見失わないまま執筆を進めることができる。執筆中に気になったときに、トピックセンテンスのリストを見直すことができれば、今何を書くべきかを常に意識して、執筆に集中できる。執筆中に全体像を意識しながら執筆することは僕にとっては不可欠だ。

今執筆しているパラグラフ以外は、トピックセンテンスのリストを表示できると理想的だ。執筆中にトピックセンテンスのみを表示できると、ん?と思ったときにぱっと全体を見渡せるようになる。エディタの機能でそれができれば、文書を手作業でいじらなくてよい。当然、復元するときのミスもない。字数のカウントも狂わない。そして、この機能は機能はエディタがやってくれるべきだ。

トピックセンテンスのリストと本文を別ファイルにするのは同期の問題がある。手動でやるならば、最初にトピックセンテンスを書いて、そのリストをコピーして、各パラグラフの内容を書き足していく方法がある。この方法だと、トピックセンテンスのみのファイルと、パラグラフの中身も書かれたファイルの2つができる。しかし、執筆中にトピックセンテンスの順序が変わることはよくあるし、センテンスに書くこと自体が変わることもある。そうすると、修正するたびに、トピックセンテンスファイルも編集しなければならない。いちいちやるのはめんどうだが、やらなければやがてトピックセンテンスファイルは「原案ファイル」とでもよぶものになり、使いにくいものになってしまう。

編集中の文章で、トピックセンテンスのみ、あるいは文章全体の表示を切り替える仕組みがあるととてもよい。やはり、トピックセンテンスのリストと文書本体は同じファイルであるべきであり、リストの表示はエディタの機能で切り替えるべきだ。

一般的なエディタで代替的な方法を考えると、文書をアウトラインで管理する方法がある。

アウトラインで管理する場合、横にウィンドウが出て、文書全体をみながら編集することも可能だ。

アウトライン機能は文書全体を見出し単位で折りたたむこともできる。

しかし、「パラグラフ」の編集をしたいときに、トピックセンテンスを見出し、それ以降の文章を本文に入れると若干見通しが悪い。あくまでもパラグラフとしてトピックセンテンスのサポートを書きたい。

見出し+本文はこんな感じだ。

上がトピックセンテンスだとすると、今書いているのがサポートだ。 これはこれで悪くないけど、やっぱり違和感はある。 ちなみに、LaTeXやMarkdown、HTMLでは、空行がパラグラフの区切りだ。 現代の文書作成には相性がいいのかもしれない。

見出し+本文のメリットはどのエディタでも使えること。

実装できるなら改行なしのほうが違和感は少ない。トピックセンテンスに続けて説明文を書きたいものだ。古い考えなんだろうか。

実装のための調査

emacs用の実装

僕が普段使っているのはemacsなので、emacs用に実装してみる。

他のエディタでも実装できるように、手順を詳しく書いておく。

指定した場所を隠したり表示したりする

emacsでは、一定範囲を隠したり、表示したりできる。

幸い、選択範囲の表示・非表示を切り替えるプログラム(fold-thisパッケージ)があった。

ドキュメントはじゅうぶんに読みやすい。

https://github.com/magnars/fold-this.el

使い方は、こちらでも紹介されている。

http://emacs.rubikitch.com/fold-this/

今回は、fold-thisとfold-this-unfold-at-point関数が使えそうだ。

fold-this関数にはリージョンの範囲を与える必要がある点だけが注意かな。

カーソルの移動関係はそう難しくない

トピックセンテンスにマッチする正規表現は?

簡単な方法としては、行頭から最初の。(句点)までをマッチさせて、その後にカーソルを移動させる。

この正規表現は、「^.*?。」でよい。

当たり前というか、簡単に実装しているので、文章中に『僕のセリフは「あれは青いと言ったはずだ。」だった。』のように、「」で囲まれた部分もヒットしてしまう。この辺は正規表現の工夫でなんとかなりそうだけど、今は簡単実装で満足しておく。

というか、僕は小説書くわけではないので。。。

実装する

目的の動作にはいくつかある

トピックセンテンス以外の表示・非表示を切り替えるために4種類の動作を定義する。

  1. あるパラグラフのトピックセンテンスのみ表示
  2. あるパラグラフ全体を表示する
  3. 文章全体でトピックセンテンスのみ表示する
  4. 文章全体を表示する

あるパラグラフのトピックセンテンスのみ表示

「トピックセンテンスのみ表示」は、「トピックセンテンス以外の部分を非表示にする」ことと同じ。

fold-thisパッケージに、fold-this関数が含まれているのでこれを使用する。

手順的には次のようにした。

  1. 現在の場所を覚える。
  2. パラグラフの先頭に移動する。
  3. パラグラフの先頭から最初の「。」(句点)までをトピックセンテンスとして認識する。正規表現では、「^.*?。」でこの目的は達成できる。emacsではre-search-forward関数に「^.*?。」を与えると、。の次にカーソル(ポイント)が移動する。
  4. トピックセンテンスの次の文字からパラグラフの終わりまでを折りたたむ。(fold-this)
  5. カーソルを元の位置に戻す

というわけで実装できた。

(defun my-fold-after-topic ()
  "fold paragraph except topic sentence."
  (interactive)
  (let ((pos (point)))
    (move-beginning-of-line nil)
    (if (re-search-forward "^.*?。" nil t) 
	(fold-this (point) (line-end-position))
    )
    (goto-char pos)
    )
  )

あるパラグラフ全体を表示する

こちらは、fold-thisに含まれるfold-this-unfold-at-point関数を活用する。

手順的には次のようにした。

  1. 現在の場所を覚える。
  2. パラグラフの先頭に移動する。
  3. パラグラフの先頭から最初の「。」(句点)までをトピックセンテンスとして認識する。正規表現では、「^.*?。」でこの目的は達成できる。emacsではre-search-forward関数に「^.*?。」を与えると、。の次にカーソル(ポイント)が移動する。
  4. ここでfold-this-unfold-at-point関数を実行して、折りたたまれているテキストを展開する。
  5. カーソルを元の位置に戻す

なお、fold-this-unfold-at-point関数は、実行した位置に折りたたまれたテキストがなければ、なにもしない。

というわけでこれも実装できた。

(defun my-unfold-paragraph ()
  "show paragraph except topic sentence."
  (interactive)
  (let ((pos (point)))
    (move-beginning-of-line nil)
    (if (re-search-forward "^.*?。" nil t) 
	(fold-this-unfold-at-point)
    )
    (goto-char pos)
    )
  )

文章全体でトピックセンテンスのみ表示する

これは折りたたむ関数がすでにできているので、テキストの各パラグラフについて折りたたみを実行すれば良い。

  1. 現在の場所を覚える。
  2. 文書(ファイル、バッファ)の最初に移動する。
  3. 現在のパラグラフにmy-fold-untopic関数を実行する
  4. 次の行に移動する
  5. 3-4を文章(ファイル、バッファ)末まで繰り返す
  6. カーソルを元の位置に戻す

実装はこうなった。

(defun my-fold-after-topic-all ()
  "show all paragraphs except topic sentence."
  (interactive)
  (let ((pos (point)))
    (goto-char (point-min))
    (while (< (point) (point-max))
      (my-fold-after-topic)
      (forward-line 1)
      )
    (goto-char pos)
  )
)

文章全体を表示する

  1. 現在の場所を覚える。
  2. 文書(ファイル、バッファ)の最初に移動する。
  3. 現在のパラグラフにmy-unfold-untopic関数を実行する
  4. 次の行に移動する
  5. 3-4を文章(ファイル、バッファ)末まで繰り返す
  6. カーソルを元の位置に戻す

上とほとんど同じだ。

なんなら、共通の関数を呼ぶようにしてオプションで切り替えてもいいぐらい。 でもまあ、それはリファクタリングのときにやろう。

(defun my-unfold-paragraph-all ()
  "show all paragraphs except topic sentence."
  (interactive)
  (let ((pos (point)))
    (goto-char (point-min))
    (while (< (point) (point-max))
      (my-unfold-paragraph)
      (forward-line 1)
      )
    (goto-char pos)
  )
)

最終的な設定

以上で期待通りの動作になったので、.emacsに設定として書き込むことにする。

leafを使った設定を掲載しておく。

(leaf fold-this
  :ensure t
  :config
  (defun my-fold-after-topic ()
    "hide paragraph except topic sentence."
    (interactive)
    (let ((pos (point)))
      (move-beginning-of-line nil)
      (if (re-search-forward "^.*?。" nil t) 
	  (fold-this (point) (line-end-position))
	)
      (goto-char pos)
      )
    )
  (defun my-unfold-paragraph ()
    "show paragraph except topic sentence."
    (interactive)
    (let ((pos (point)))
      (move-beginning-of-line nil)
      (if (re-search-forward "^.*?。" nil t) 
	  (fold-this-unfold-at-point)
	)
      (goto-char pos)
      )
    )
  (defun my-fold-after-topic-all ()
    "hide paragraph except topic sentence."
    (interactive)
    (let ((pos (point)))
      (goto-char (point-min))
      (while (< (point) (point-max))
	(my-fold-after-topic)
	(forward-line 1)
	)
      (goto-char pos)
      )
    )
  (defun my-unfold-paragraph-all ()
    "hide paragraph except topic sentence."
    (interactive)
    (let ((pos (point)))
      (goto-char (point-min))
      (while (< (point) (point-max))
	(my-unfold-paragraph)
	(forward-line 1)
	)
      (goto-char pos)
      )
    )
  :bind (
	 ("C-c f f" . my-fold-after-topic)
         ("C-c f u" . my-unfold-paragraph)
         )
)
Hugo で構築されています。
テーマ StackJimmy によって設計されています。