Closed20

org-mode をもう一度 (→ 全てを管理しない管理策)

toyboot4etoyboot4e

このスクラップは

単なるマークアップ言語を超えた org-mode の姿を目にするために、様々な org-* を試していくノートです。すべてがリンクした世界まで行くぞ!!

toyboot4etoyboot4e

Emacs 29: プロファイリングの切り替え

まずは足元を固めていきます。以前はプロファイル (init.el のディレクトリ) の切り替えのため chemacs2 を使用していました。 Emacs 29 には emacs --init-directory <path> で切り替えができるようです。 restart-emacs も生えたとか。

年越しに Emacs 29 に乗り換えていきます:

$ sudo nix-channel update
$ sudo nixos-rebuild switch
$ emacs --version
GNU Emacs 29.1
Copyright (C) 2023 Free Software Foundation, Inc.
GNU Emacs comes with ABSOLUTELY NO WARRANTY.
You may redistribute copies of GNU Emacs
under the terms of the GNU General Public License.
For more information about these matters, see the file named COPYING.

楽ちん! Head build もできるのでしょうか。

アップデート後は以下のエラーにあたりました。ひとまず以下を追加しましたが、ちゃんと issue を読まないと……

(defvar native-comp-deferred-compilation-deny-list nil)

https://github.com/doomemacs/doomemacs/issues/5682

toyboot4etoyboot4e

起動用スクリプト

猛烈な反対に遭いそうですが、僕は Emacs をターミナルで起動します:

em
#!/usr/bin/env bash

dir="$HOME/dotfiles/editor/emacs-leaf"
CARGO_TARGET_DIR=target/ra emacs -nw --init-directory "$dir" "$@"

起動スクリプトに bash を挟まないようにした方が良いのかもしれません。

toyboot4etoyboot4e

Zenn Book を org-mode で書くには

まだまだ足元を固めていきます。 conao3/ox-zenn.el を使えば、単ファイルの変換 (.org -> .md) はできそうです。 Book 全体を変換するコマンド (interactive な関数) を作ってみます。

インストール

まずは leaf.el を使って ox-zenn をインストールします。こんな形で org-export-with-toc をセットして良いのでしょうか:

(leaf ox-zenn
    :url "https://github.com/conao3/ox-zenn.el"
    :custom ((org-zenn-with-last-modified . nil)
             (org-export-with-toc . nil)))

既存のコマンド

ox-zenninteractive コマンドは以下の 3 つです:

  • org-zenn-export-to-markdown: バッファに出力
  • org-zenn-export-as-markdown: ファイルに出力 (パスはファイル中のプロパティで指定)
  • org-zenn-convert-region-to-md: 選択範囲の org 構文を markdown に変換する。 Zenn スクラップでこれを使いたい気がしますが……?

コマンド作成

単ファイル変換 (出力先を指定)

この部分を抜き出せば良さそうです:

(let ((outfile (org-export-output-file-name ".md" subtreep)))
    (org-export-to-file 'zennmd outfile async subtreep visible-only))

Book 全体の変換コマンドの作成

止めた方法 (自作関数)

以下の org-zenn-export-book~/zenn/books-org/my-book のようなパスを渡すと、 ~/zenn/books/my-book 以下に .md ファイルを出力してくれます:

  (leaf ox-zenn
      :url "https://github.com/conao3/ox-zenn.el"
      :custom ((org-zenn-with-last-modified . nil)
               (org-export-with-toc . nil))
  
      :config
      (defun org-zenn-export-to-markdown-as (outfile &optional async subtreep visible-only)
          (interactive "sOutfile: ")
          (org-export-to-file 'zennmd outfile async subtreep visible-only))
  
      (defun org-zenn-export-book (&optional org-dir)
          "`$org-dir' -> `$md-dir': `$zenn/books-org/$book' -> `$zenn/books/$book'"
          (interactive "sBook directory: ")
          (setq org-dir (expand-file-name (or org-dir ".")))
          (let* ((book (file-name-nondirectory org-dir))
                 (zenn (file-name-directory (directory-file-name (file-name-directory org-dir))))
                 (md-dir (concat zenn "books/" book "/")))
              (if (not (and (file-directory-p org-dir) (file-directory-p md-dir)))
                      (message "Not an org book directroy?")
                  (dolist (src-file-name (seq-filter (lambda (s) (string-suffix-p ".org" s)) (directory-files org-dir)))
                      (let* ((src-file (concat org-dir "/" src-file-name))
                             (dst-file-name (concat (file-name-base src-file-name) ".md"))
                             (dst-file (concat md-dir dst-file-name)))
                          (with-temp-buffer
                              (insert-file-contents src-file)
                              (org-zenn-export-to-markdown-as dst-file nil nil nil))))))))

ox-zenn の機能を使うのが良さそうです

conao3 さんのこういう発想は最高ですね。いずれ僕も 1 book = 1 org ファイル 程度の集約は試してみたいです。

追記: 元ネタは ox-hugo だったとか。

toyboot4etoyboot4e

1 つの org ファイルを 1 つの book として export する

予定するソースファイル

各章をレベル 1 の見出しとして書きます。出力ファイル名を :EXPORT_FILE_NAME: プロパティで設定し、出力ディレクトリは #+BOOK_DIR で決めます。 #+LINK は全章で共通の値を使います。

#+TITLE: Learn Git via Magit
#+BOOK_DIR: ../books/learn-git-via-magit
#+LINK: magit https://github.com/magit/magit

#+CAPTION: config.yaml
#+BEGIN_SRC yaml
#+END_SRC

* 環境構築
:PROPERTIES:
:EXPORT_FILE_NAME: ../books/learn-git-via-magit/setup
:END:

* コンフリクトの解消
:PROPERTIES:
:EXPORT_FILE_NAME: ../books/learn-git-via-magit/conflicts
:END:

Level 1 の見出しを export する

#+BOOK_DIR を反映するため、手動で cd します。 org-map-entries によって、 Level 1 の見出しのすべてに org-export-to-markdown を走らせることができました。

(defun org-zenn-export-buffer-to-book (&optional org-dir)
    "Runs subtree export to each level 1 headings. Respects `#+BOOK_DIR'."
    (interactive)
    (let* ((dir default-directory)
           (pub-dir (car (cdr (car (org-collect-keywords '("BOOK_DIR" "XYZ")))))))
        ;; cd into the target directory, but manually:
        (when pub-dir (cd pub-dir))
        ;; export all the top level headings and come back:
        (unwind-protect
                (org-map-entries (lambda () (org-zenn-export-to-markdown nil t)) "LEVEL=1")
            (when pub-dir (cd dir)))))
toyboot4etoyboot4e

config.yaml を自動生成する

config.yaml を tangle する

* =config.yaml=

#+BEGIN_SRC yaml :tangle ../books/kyopro-bonsai-hs/config.yaml
title: "競プロ盆栽.hs"
summary: "Haskell の AtCoder 用自作ライブラリ解説"
topics: ["haskell", "atcoder"]
published: false
price: 0
chapters:
- intro
#+END_SRC

チャプター一覧の生成を自動化する

no-web syntax にてコードブロックの評価結果を tangle できます

* =config.yaml=

#+BEGIN_SRC yaml :tangle ../books/kyopro-bonsai-hs/config.yaml
title: "競プロ盆栽.hs"
summary: "Haskell の AtCoder 用自作ライブラリ解説"
topics: ["haskell", "atcoder"]
published: false
price: 0
#+END_SRC

#+NAME: zenn-headings
#+BEGIN_SRC elisp
(princ "chapters:\n")

(org-map-entries
  (lambda ()
    (let ((title (org-entry-get nil "EXPORT_FILE_NAME")))
      (when title (princ (concat "- " title "\n")))))
  "LEVEL=1")
#+END_SRC

#+BEGIN_SRC yaml :noweb yes :tangle ../books/kyopro-bonsai-hs/config.yaml
<<zenn-headings()>>
#+END_SRC

:DRAFT: 判定付きのチャプター生成

#+NAME: zenn-headings
#+BEGIN_SRC elisp
(princ "chapters:\n")

(org-map-entries
  (lambda ()
    (let* ((title (org-entry-get nil "EXPORT_FILE_NAME"))
           (is-draft (org-entry-get nil "DRAFT")))
      (when (and title (not is-draft))
        (princ (concat "- " title "\n")))))
  "LEVEL=1")
#+END_SRC

#+TITLE: を使う

TODO

toyboot4etoyboot4e

org -> markdown の変換がうまく行かないケース

改行 (\\)

org-mode では行末の \\ が line break として扱われます。しかし markdown に変換する時に \\ が改行として解釈されないようです。

直接 markdown として書いてしまえばなんとかなります。

#+BEGIN_EXPORT md
1. Buffer  
aa

2. BinaryHeap  
bb
#+END_EXPORT
toyboot4etoyboot4e

org-mode は手順書の夢を見るか

org-mode を作業記録に使った際の追跡性の高さに注目したいと思います。

リンク機能

行き過ぎた org-mode は、何でもリンクしてしまうのが強みです。カレンダーと日報や TODO リストがリンクされていたり、知識の断片である org-roam ファイルに (?) 飛んだり、おおよそブラウザや SNS アプリ以外のほぼすべてが Emacs の中で繋がるとか。

GitHub のソースにも簡単にリンクできるようです。コードリーディングの際は org-mode でノートを取っておくだけで、誰かとコードの追跡を共有できます。将来的に自分が見返すこともあるでしょうから、この追跡性を標準にしない手は無いです。

https://zenn.dev/eggc/articles/63f5f3fe74ac46

さて試しにソースファイルへのリンクを org-store-link で取得し、 org-insert-link (C-c l) から挿入してみました:

[[file:~/dev/hs/toy-lib/src/Data/Vector/IxVector.hs::generateIV :: (Unindex i, U.Unbox a) => (i, i) -> (i -> a) -> IxUVector i a][generateIV]]

行指定には、番号ではなくテキストの内容を使う (こともできる) ようです。また org-insert-link の際には org-store-link の履歴が参照されるため、 org-store-link を専用コマンドにする意義があります。

TODO: ローカルのファイルではなく upstream リポジトリへのリンクを取得し保存したい

コードブロックの評価 (org-babel)

言語紹介で REPL を叩いてみせることがありますが、実際に .org ファイルに書いたコードブロックを評価してその結果を吐かせることで、結果の正しさが保証されます。あるいは作業ログとして org-mode が使えることになります。『ガチ言語 Haskell』を書くときも org-babel を使っていれば良かったですね。

#+BEGIN_SRC haskell :results output
import Data.List
scanl' (+) (0 :: Int) [1, 2, 3, 4, 5]
#+END_SRC

#+RESULTS:
: [0,1,3,6,10,15]

あるいはファイル全体で :results output を指定すると:

#+PROPERTY: header-args :results output

#+BEGIN_SRC haskell
import Data.List
scanl' (+) (0 :: Int) [1, 2, 3, 4, 5]
#+END_SRC

#+RESULTS:
: [0,1,3,6,10,15]
  • TODO: import などを外だしする
  • TODO: literate haskell
toyboot4etoyboot4e

org-capture

機能

規定ファイルに追記するための API のようです。たとえば task.org にささっと TODO を追加する際に使えます。

主な設定

Setting up capture の通り、規定ファイル名を変更できます:

(setq org-default-notes-file (concat org-directory "/notes.org"))

Capture templates で task 以外の挿入もできるようになりそうです。

ショートカット

org-capture のメリットとしては、 .org ファイル以外の編集中にも気軽に使用できます。そのため The Org Manual においても グローバルなショートカットキーの設定が提案されています:

(global-set-key (kbd "C-c l") #'org-store-link)
(global-set-key (kbd "C-c a") #'org-agenda)
(global-set-key (kbd "C-c c") #'org-capture)
  • org-store-link はカーソル下へのリンクを (どこへ?) 保存します。後から org-insert-link で貼り付けできるようになります。
  • org-agenda は調査中です。
  • org-capture は冒頭の通りです。

evil-collection には org-capture のショートカット設定がありませんでした。やはりグローバルなキーバインドを設定するのが良さそうです。

Journal ファイルへの追記

Doom Emacs の j

TODO

toyboot4etoyboot4e

見出しの畳み込み

org-cycle による畳み込み状態の変更

見出しの上にカーソルがある場合、 org-cycle によって畳み込み・展開をトグルできます。

org-cycle-emulate-tabnil に設定しておくと、カーソルが本文中にある場合も外側の見出しをトグルできるようになります。

;; fold
(evil-define-key 'normal org-mode-map
    "za" #'org-cycle
    "zR" #'org-fold-show-all
    ;; close/open
    "zC" #'org-fold-hide-sublevels
    "zO" #'org-fold-show-subtree
    )

すべての見出しの状態を変える

S-TAB でサイクルします。

一定レベルまでの畳込みに切り替える

Vim では z1, z2 .. のようなキーバインドで畳み込みのレベルを一括変更する方法が一般的だったと思います。たぶん。

このようなキーバインドを作るためには、 outline-hide-sublevels 関数を呼び出せば良いです。 TODO: 本文を展開しつつ subheading は展開しない? ← 別に本文を展開しなくて良い気がしてきた

親見出しを畳む

C-c C-u で親見出しまで移動して畳めば良さそうです。

toyboot4etoyboot4e

TODO リストと org-agenda

大体いつもこの辺りで飽きます。今度こそ予定管理を org-mode に移行できるのか……?

基本的な機能

これを見るとイメージが掴みやすいと思います。以外とできることは少ない気も。

https://orgmode.org/worg/org-tutorials/orgtutorial_dto.html

TODO リスト、タイムスタンプなど

見出しの挿入

  • M-RET
  • C-RET

見出しの移動

  • M-十字キー: 見出しの移動
  • M-S-十字キー: 見出しの移動 (小見出し付き)

見出しへのカーソル移動

  • C-c C-n: outline-next-visible-heading
  • C-c C-n: outline-previous-visible-heading

TODO の挿入

  • M-S-RET

タスクの状態

アイテムの状態は手書きできるほか、 org-todo (C-c C-t) や org-shiftright (S-<right>) などから変更できます:

;; `"|"' 以降は done 状態。 KILL は取り消し
;; https://www.karelvo.com/blog/2023-02-17-orgmode/
(setq org-todo-keywords '((sequence "TODO(t)" "WAIT(w)" "|" "DONE(d)" "KILL(k)")))

すべての子タスクが DONE になったら親タスクを DONE にする

https://orgmode.org/manual/Breaking-Down-Tasks.html

Priorities

タスクが山積みになってくると、優先度の高いタスクを agenda の上の方に表示してほしくなります。タスクの優先度は A, B, C で、無表記のタスクは優先度 B です。

  • S-<up> (org-shiftup): 優先度を上げます
  • S-<down>: org-shift-down: 優先度を下げます

タイムスタンプ

タスクの予定はタイムスタンプとして入力します。タイムスタンプを入力するのは org-time-stamp (C-c .) です。 Evil においては insert モードで呼び出しすると、挿入位置がズレずに済みます:


Shift + 上下左右でカーソル移動

**** TODO 歯医者に行く <2024-01-20 Sat> :life:
DEADLINE: <2024-01-24 Wed>

カレンダーを月曜日から始める:

(setq-default calendar-week-start-day 1)

C-c . の 2 連続実行で時間範囲を指定できます: <2024-01-24 Wed>--<2024-01-31 Tue>

agenda に表示されないタイムスタンプは inactive と称され C-c ! で挿入できます (org-time-stamp-inactive) 。 Inactive なタイムスタンプは [2024-01-24 Wed] のように角カッコで囲われます。

タグ

タスクに対してはタグ (:life など) の設定ができます。タグは C-c C-q (org-set-tags-command) から付与できます。また 親見出しのタグは小見出しに引き継がれます

deadline, schecule

タスクに対しては schedule (C-c s) や deadline (C-c d) が設定できます。 Schedule は開始日、 deadline は完了日で、いずれも agenda に出てくるようです。

log

タスクの状態を DONE に切り替えた際に、自動的に完了時間を挿入することができます。必要なら有効化します:

(setq org-log-done 'time)

org-agenda

org-agenda を使えば、所定の .org ファイルから集めた予定データの一覧が見られます。

    (setq org-agenda-files
          (mapcar (lambda (path) (concat org-directory path))
                  '("/agenda.org"
                    "/journal.org")))

agenda がめちゃめちゃ見づらい。と思いきや、予定が増えてくるとタイムラインが格好いいですね。

  • TODO: タイムラインを出す?
  • TODO: 次の周へ移動、月単位表示、日単位表示など
  • TODO: タスクのソート

org-super-agenda

タグや日付で agenda のグループ分けが成されるようになります。たとえばこんな感じ:

(leaf org-super-agenda
    :url "https://github.com/alphapapa/org-super-agenda"
    :config
    (setq org-super-agenda-groups
          '((:name "Next to do"
                   :todo "next"
                   :order 1)
            (:name "Due Today"
                   :deadline today
                   :order 2)
            (:name "Devlog"
                   :tag "devlog"
                   :order 50)
            (:name "Book"
                   :tag "book"
                   :order 100)))

    (org-super-agenda-mode 1))

TODO: イカしたすくりーんしょっt

月単位の目標管理を org-agenda に表示する

TODO

https://www.philnewton.net/blog/org-agenda-monthly-goals/

年単位の目標管理を org-agenda に表示する

TODO

(複数) タグ持ちの agenda 全部見る

たとえば :output タグ付きのタスクを、さらに :devlog: などタグ分けする。タスクを保存するファイルで分けても良いが、あえてタグで分けたい。

TODO

toyboot4etoyboot4e

babel, tangle

シェルを実行してみる

まずは実行可能な言語の設定を行い:

(org-babel-do-load-languages
 'org-babel-load-languages '((ditaa . t)
                             (dot . t)
                             (shell . t)))

適当な source block を作って org-ctrl-c-ctrl-c (C-c C-c) で実行すると、 RESULTS ブロックが出力されます:

#+BEGIN_SRC shell
echo test
#+END_SRC

#+RESULTS:
: test

RESULTS: ブロックは org-export では出力されないようです。コードブロックとして表示するには、 #:BEGIN_SRC にて :exports both を指定します:

-#+BEGIN_SRC shell :results output
+#+BEGIN_SRC shell :results both

https://orgmode.org/manual/Exporting-Code-Blocks.html

プロジェクト作成時のシェルの実行履歴を残してみる

TODO: cwd

ファイルを出力する (org-tangle)

単ファイルの出力

次のようなコードブロックの中身をファイルへ出力できます。複数のファイルを 1 つのドキュメントに収めることができ、順を追った解説に適しているかもしれません。

#+BEGIN_SRC sh :tangle ./example.sh
echo 'Hello, world!'
#+END_SRC
  • C-c C-t v: org-babel-tangle
    バッファの全ブロックの `:tangle: を実行

1 つのファイルへ延々と追記する

Nix と組み合わせてみる?

toyboot4etoyboot4e

シェルの実行履歴を残す

そもそも org-mode 無しでもログ記録の開始・終了を可能にしたいが……。

TODO. cwd のセットなど?

toyboot4etoyboot4e

満足

そんなに新機能が欲しいことも無いので閉じます。

このスクラップは4ヶ月前にクローズされました