🐡

pythonデータ分析のためのemacs設定の大枠とemacs-jupyterによるjupyter-REPLの紹介

2024/10/27に公開

はじめに

普段emacsではpythonを使用してデータ分析や、orgを使用してタスク管理、メモ書きをしている。 emacsでデータ分析を行う上でのpython関連の設定に何が必要かの大枠を示し、 あまり記事がないemacs-jupyterによる画像が表示できるjupyter-REPLを紹介したい。

注意:パッケージ名は本来jupyterだと思われるが、jupyterだと他のjupyterと混同するので、この記事ではorganization名のemacs-jupyterと呼ぶ (https://github.com/emacs-jupyter/jupyter)
注意2:emacs-jupyterはパッケージ名(本来はjupyter)で、jupyter-REPLはemacs-jupyterにより生成されるREPLバッファ

本記事ではOS:ubuntu22, Emacs:29.4で検証を行った。

emacsを使う理由

  • python実行と、orgによるタスク管理・ドキュメント作成を同じemacs内で行えるため
  • REPL駆動開発ができるから
    • これについては、何年か前からvscodeでもできるようになったと聞いたことがある
  • デフォルトでも豊富なショートカットやコマンド
  • shellもバッファなのでshell出力結果の検索やコピーが容易

ワークフローの整理とパッケージの選定

データ分析の各stepとemacsパッケージ候補は以下

stage 使用するemacsパッケージの候補
要件の整理 org
探索的データ分析(EDA) emacs-jupyter, ein, (EAF)
スクリプトの開発 emacs-jupyter, eglot, elpy, その他多数
実験、結果の管理、メモ書き org
ドキュメント作成 org, org-babel, ein, (EAF)

パッケージ選定の結論

stage 使用するemacsパッケージと使い方
要件の整理 orgに書いていく
探索的データ分析(EDA) .pyのコードをemacs-jupyterを用いて*jupyter-repl*に送り実行する
可視化結果画像はorgに貼っていく
LSPにeglotを用いIDE機能を用いる
スクリプトの開発 .pyのコードをemacs-jupyterを用いて*jupyter-repl*に送りREPL駆動開発を行う
eglot/pyrightやその他ツールを用いIDE機能を実行
実験、結果の管理、メモ書き orgに書いていく
ドキュメント作成 コード不要な場合orgで作成
コードが必要な場合einでjupyter notebookを編集する(IDE機能はelpyを用いる)

emacs-jupyterをEDAとスクリプト開発で使用することを想定している。

以下にパッケージ選定理由を記す

探索的データ分析(EDA)、可視化

汚いコードでも良いから、どんどん可視化していく段階

以下の候補

  1. emacs-jupyterで.pyのコードをjupyter-REPLに送り、画像を可視化していく
    • コードと画像出力を同じ箇所にまとめることができない
      • そのため必要なものはorgに貼っていくことが必要
    • .pyはLSPやelpyによるIDE機能を使うことができる
  2. einでjupyter notebookを編集する
    • LSPによるIDE機能を使うことができない
    • elpyによるIDE機能を使うことができるが、遅い(らしい)
  3. EAF(Emacs Application Framework)でjupyter notebookを編集する
    • 時間切れで試せていない
  4. org-babelでorg中でEDAを行う
    • org-babelはきれいにまとめるときには良いが、試行錯誤の途中には操作感が合わない気がしている。

選定:迷うがこの段階の結果がノートブック形式で残っていることは必須ではないと考え、emacs-jupyterを使うことにする emacs-jupyterを用いて、.pyのスクリプトをjupyter-REPLに送り可視化する。必要な図はorgに貼っていく。 (この記事の次の章で説明する)

スクリプトの作成

  • マスト
    • emacs-jupyterで.pyのコードをjupyter-REPLに送り、REPL駆動開発を行う
  • 候補
    1. elpyによるIDE機能を使う
    2. LSPによるIDE機能を使う

pythonの開発において、コードの断片をREPLに送り込んで挙動が正しいか確認しながら開発する方式(REPL駆動開発)は実装の効率が良いと個人的に感じている。 REPLに送り込むとき、画像の可視化もできると、可視化コードの開発もしやすく、emacs-jupyterをマストで採用する。

現状elpyとLSPのどちらが優れているか筆者には不明。 elpyに慣れていて不満を感じていないならばelpyを使い続ければ良いと考える。 ただ、今からemacsを使い始めるのであれば、エディタに縛られずに機能開発をする性質から、より大量のメンテナがいるLSPを使うのが良いと考えられる。

選定: emacs-jupyterによるREPL駆動開発と、LSPによるIDE機能の使用

ドキュメント作成

  • コード不要な場合
    • orgで文章をまとめる
  • 整形したコードつきで説明する場合
    • einでjupyter notebookを編集する
      • IDE機能はelpyを用いる
    • EAFでjupyter notebookを編集する
      • 時間切れで試せていない
    • org-babelで文芸プログラミングをする

org-babelによる文芸プログラミングは、ソースブロックごとに毎回#+ATTRを設定するのが個人的には面倒で、コードを実行するならjupyter notebookで良いのではないかと思ってしまう。

選定:

  • コードなしで良い場合:orgで文章を作成し、html等にエクスポート
  • コードありで説明する場合:einでjupyter notebookを編集する

jupyter-REPLを用いたEDA

.pyのスクリプトをjupyterに送る。 jupyter-REPLによるbufferでは画像の表示ができるのが良い。また、htmlの表示もできる。 ただし、plotly等のマウス箇所にホバリングして情報表示には対応していないと思われる(少なくとも試した限りではできなかった)。

init.elの設定

(leaf *jupyter
  :config
  (leaf jupyter
    :ensure t
    :defvar jupyter-repl-echo-eval-p
    :config
    (setq jupyter-repl-echo-eval-p t)
    )
  (leaf zmq :ensure t))

ただし、zmqのビルドが必要。上記でzmqをインストールしたあと、zmqのビルドを行う。

$ cd ~/.emacs.d/zmq-20220510.1820/
$ sudo apt-get install autoconf automake libtool
$ make

使い方

.pyを開き M-x jupyter-run-replを実行

すると下記の画面右側のような jupyter-repl bufferが起動する。 つぎに、C-c C-bで.pyのコードをバッファに送ると、下記のようにmatplotlib等による画像が表示される。

C-c C-cでカーソル行あるいは、選択リージョンのコードをREPLで実行できる。

可視化結果はこのままだと流れていってしまうので、生成した画像を手早くorgに貼って観察結果をメモしていきたい。 しかし、要件にあうコマンドが見つからなかったので、簡単なlispコードを作成した。 (実際にはコードはperplexityに書いてもらった。)

(defun my-image-save ()
  "Save the image under point to the '.fig' directory with a timestamp filename.
Creates the '.fig' directory if it doesn't exist.
Copies the full path of the saved image to the clipboard."
  (interactive)
  (let* ((fig-dir ".fig")
         (out-f (format-time-string (concat fig-dir "/%Y%m%d-%H%M%S.png")))
         (full-path (expand-file-name out-f)))
    ;; Create '.fig' directory if it doesn't exist
    (unless (file-exists-p fig-dir)
      (make-directory fig-dir t)
      (message "Created directory: %s" (expand-file-name fig-dir)))
    ;; Save the image
    (image-save-with-arg out-f)
    ;; Copy the full path to clipboard
    (kill-new full-path)
    ;; Message to inform user
    (message "Image saved and full path copied to clipboard: %s" full-path)    
    ;; Return the full path of the saved image
    full-path))

(defun image-save-with-arg (&optional file)
  "Save the image under point.
This writes the original image data to a file.  Rotating or
changing the displayed image size does not affect the saved image.
If FILE is provided, save to that file. Otherwise, prompt for a filename."
  (interactive)
  (let ((image (image--get-image)))
    (with-temp-buffer
      (let ((image-file (plist-get (cdr image) :file)))
        (if image-file
            (if (not (file-exists-p image-file))
                (error "File %s no longer exists" image-file)
              (insert-file-contents-literally image-file))
          (insert (plist-get (cdr image) :data))))
      (let ((save-file (or file
                           (read-file-name "Write image to file: "))))
        (write-region (point-min) (point-max) save-file)
        (message "Image saved to %s" save-file)))))

(defun my-image-yank ()
  "Insert an Org mode file link for the image path in the clipboard at the current cursor position.
Only insert if the file is an image (png, jpg, jpeg, gif, or svg)."
  (interactive)
  (let ((file-path (substring-no-properties (current-kill 0))))
    (if (string-match-p "\\.\\(png\\|jpe?g\\|gif\\|svg\\)$" file-path)
        (let ((relative-path (file-relative-name file-path)))
          (insert (format "#+ATTR_HTML: :width 300\n[[file:%s]]" relative-path))
          (org-redisplay-inline-images))
      (message "Clipboard content is not a supported image file path. No insertion performed."))))

上記の関数をinit.elに入れておく。 jupyter bufferで画像にカーソルを当て、M-x my-image-copyの実行により、画像を保存し画像のフルパスをクリップボードに保存できる。

保存先はバッファのディレクトリに .fig/ というディレクトリを作成し、現在の日時(秒数まで)でファイル名を作成する。 my-image-copyでクリップボードに出力される

例:/home/{username}/work/2024/emacs-python/.fig/20241027-081901.png

my-image-copy実行の様子

次に M-x my-image-yankで、クリップボードの画像へのパスを相対パスに直し、orgで画像を表示できる形式に直して貼り付ける。

my-image-yankの出力例:

#+ATTR_HTML: :width 300
[[file:.fig/20241027-084756.png]]

実際にはこのように表示される

貼り付け時に(org-redisplay-inline-images)で画像を表示するようにしている。 画像を非表示にしたい場合は、C-c C-x C-vを実行する。 画像のサイズを変えたい場合は、:width 300の300の部分を適宜変更する。

以上で、emacs-jupyterを用いて.pyにコードを書きながらEDAができることを確かめた。 画像が表示できるREPLとしてemacs-jupyterによる方法を紹介し、可視化画像を手軽にorgに保存して表示するための関数を作成した。

【補足】pyenvで作成したpython環境の登録

python -m ipykernel install --user --name={name} --display-name={display-name}

で、kernelに環境を登録しておけば自分の環境では、M-x jupyter-run-repl起動時に環境の選択ができた
参考:https://qiita.com/yuki-shark/items/fba874a109f04004158b

【参考】plotly等のhtml形式の可視化の表示

plotlyから作成されるhtmlもjupyter-REPLで可視化できることを確認している。 (ただしホバリングはできなかった。)

emacs-jupyterのxwidets機能ビルド

# installs nvm (Node Version Manager)
$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.0/install.sh | bash
$ nvm install 20

https://github.com/emacs-jupyter/jupyter/issues/333 のコードを参考に、下記の設定を仕込んだ。

import os
from hashlib import md5

import plotly.graph_objects as go
import plotly.io as pio
from IPython.display import FileLink, Image


def myshow(self, *args, **kwargs):
    html = pio.to_html(self)
    mhash = md5(html.encode("utf-8")).hexdigest()
    if not os.path.isdir(".ob-jupyter"):
        os.mkdir(".ob-jupyter")
    fhtml = os.path.join(".ob-jupyter", mhash + ".html")

    with open(fhtml, "w") as f:
        f.write(html)

    display(FileLink(fhtml, result_html_suffix=""))
    return Image(pio.to_image(self, "png"))


go.Figure.show = myshow

import numpy as np

N = 1000
t = np.linspace(0, 10, 100)
y = np.sin(t)

fig = go.Figure(data=go.Scatter(x=t, y=y, mode="markers"))

fig.show()

上記のコードが下のようにjupyter-REPLで実行できることを確認している。

ただしこの方法には以下の問題がある

  • 毎回この設定を実行する必要がある
  • 結局ホバリングが使えないと思われる

また、上記のhtmlも同様に保存してorgで表示可能にできると予想しているが、その設定は作れていない。

大人しく以下の設定を行い、ブラウザに表示させるのも一つの手である。

import plotly.io as pio
pio.renderers.default = "browser"

(https://github.com/emacs-jupyter/jupyter/issues/310)より

おわりに

まとめ

  • pythonを用いたデータ分析関連作業の整理から必要なパッケージの対応を考えた。
  • あまり記事の少ないemacs-jupyterについて、REPL機能とorgへの画像貼り付けを容易にする自作関数を紹介した
  • 結局、LSPを使うとしても、emacs内でjupyter notebookを編集するならeinやelpyが必要ということが分かった。
  • 今後のemacs界への期待
    • jupyter notebook編集時にLSPによるIDE機能が使えるようになると嬉しい
    • emacs内でプロットのホバリングが確認できると嬉しい

感想

  • lispやemacsに詳しくないこともあり、分かった感がない。多分「やさしいEmacs-Lisp講座」を読む必要がある。
  • emacsに限らず環境設定が整備されていると忙しいときの時間ロスがなくなるので、時間があるときに設定を進めるのは良いことだと認識した。
  • LLMにelispを書いてもらえるので、設定のハードルが下がった。

参考資料

  • Jupyter in the Emacs universe

    • パッケージ選定にあたりかなり参考にした
  • emacs-jupyter/jupyter

    • emacs-jupyterパッケージのgithub
    • コマンド等の使い方が記載されている
  • Gettting Things Done Simply – With Org Mode

    • 画像を貼り付ける先のorgにも何かしらの構成が合ったほうが良い
    • 筆者はこのリンク先の構成を参考に作業を行っている。シンプルなので参考にしやすいと思う。Tasks下に作業内容を書いておき、得られた可視化画像もこの各Taskに入れていく。終わったタスクは順次上のlog下に入れていくようにしている。
    • リンク先より画像を引用

本記事では触れていないがLSP/IDE関連の設定にあたっては以下を参考にした

【Appendix】環境構築

参考までに設定ファイルinit.elと検証したemacsのビルド方法を示す

init.el

  ;;; Code:
  (eval-and-compile
    (when (or load-file-name byte-compile-current-file)
      (setq user-emacs-directory
            (expand-file-name
             (file-name-directory (or load-file-name byte-compile-current-file))))))

  (eval-and-compile
    (customize-set-variable
     'package-archives '(("eorg"   . "https://orgmode.org/elpa/")
                         ("melpa" . "https://melpa.org/packages/")
                         ("gnu"   . "https://elpa.gnu.org/packages/")))
    (package-initialize)
    (unless (package-installed-p 'leaf)
      (package-refresh-contents)
      (package-install 'leaf))

    (leaf leaf-keywords
      :ensure t
      :init
      ;; optional packages if you want to use :hydra, :el-get, :blackout,,,
      (leaf hydra :ensure t)
      (leaf el-get :ensure t)
      (leaf blackout :ensure t)

      :config
      ;; initialize leaf-keywords.el
      (leaf-keywords-init)))

  ;; ここにいっぱい設定を書く
  (leaf leaf
    :config
    (leaf leaf-convert :ensure t)
    (leaf leaf-tree
      :ensure t
      :custom ((imenu-list-size . 30)
               (imenu-list-position . 'left))))

  (leaf macrostep
    :ensure t
    :bind (("C-c e" . macrostep-expand)))


  (leaf leaf-convert
    :setq ((ring-bell-function quote ignore)))


  (leaf *language-settings
    :config
    (set-language-environment 'Japanese) ;言語を日本語に
    (prefer-coding-system 'utf-8) ;極力UTF-8を使う
    (leaf mozc ;; Mozc setting
      :ensure t
      :config
      (setq default-input-method "japanese-mozc")
      (global-set-key (kbd "C-o") 'toggle-input-method)
      (define-key mozc-mode-map (kbd "C-o") 'toggle-input-method)
      )
    )

  (leaf files
    :doc "file input and output commands for Emacs"
    :tag "builtin"
    :custom `((auto-save-timeout . 15)
              (auto-save-interval . 60)
              (auto-save-file-name-transforms . '((".*" ,(locate-user-emacs-file "backup/") t)))
              (backup-directory-alist . '((".*" . ,(locate-user-emacs-file "backup"))
                                          (,tramp-file-name-regexp . nil)))
              (version-control . t)
              (delete-old-versions . t)))

  (leaf *org
    :config
    (setq org-image-actual-width nil))

  (leaf elpy
    :ensure t
    :config
    (setq elpy-shell-get-or-create-process nil))

  (leaf ein
    :ensure t
    :custom
    (ein:worksheet-enable-undo . t)
    (ein:output-area-inlined-images . t)  
    (ein:jupyter-default-server-command . "~/.pyenv/shims/jupyter")
    :hook
    (ein:notebook-mode . (lambda ()
                           (elpy-mode 1)
                           (elpy-enable))))

  (leaf *jupyter
    :config
    (leaf jupyter
      :ensure t
      :defvar jupyter-repl-echo-eval-p
      :config
      (setq jupyter-repl-echo-eval-p t)
      ;:hook
      ; これを入れるとREPLにコードを送る挙動がおかしくなる
      ;(jupyter-repl-mode . (lambda () (display-line-numbers-mode -1))))
      )
    (leaf zmq :ensure t))

(defun image-save-with-arg (&optional file)
  "Save the image under point.
This writes the original image data to a file.  Rotating or
changing the displayed image size does not affect the saved image.
If FILE is provided, save to that file. Otherwise, prompt for a filename."
  (interactive)
  (let ((image (image--get-image)))
    (with-temp-buffer
      (let ((image-file (plist-get (cdr image) :file)))
        (if image-file
            (if (not (file-exists-p image-file))
                (error "File %s no longer exists" image-file)
              (insert-file-contents-literally image-file))
          (insert (plist-get (cdr image) :data))))
      (let ((save-file (or file
                           (read-file-name "Write image to file: "))))
        (write-region (point-min) (point-max) save-file)
        (message "Image saved to %s" save-file)))))

(defun my-image-save ()
  "Save the image under point to the '.fig' directory with a timestamp filename.
Creates the '.fig' directory if it doesn't exist.
Copies the full path of the saved image to the clipboard."
  (interactive)
  (let* ((fig-dir ".fig")
         (out-f (format-time-string (concat fig-dir "/%Y%m%d-%H%M%S.png")))
         (full-path (expand-file-name out-f)))
    ;; Create '.fig' directory if it doesn't exist
    (unless (file-exists-p fig-dir)
      (make-directory fig-dir t)
      (message "Created directory: %s" (expand-file-name fig-dir)))
    ;; Save the image
    (image-save-with-arg out-f)
    ;; Copy the full path to clipboard
    (kill-new full-path)
    ;; Message to inform user
    (message "Image saved and full path copied to clipboard: %s" full-path)    
    ;; Return the full path of the saved image
    full-path))

(defun my-image-yank ()
  "Insert an Org mode file link for the image path in the clipboard at the current cursor position.
Only insert if the file is an image (png, jpg, jpeg, gif, or svg)."
  (interactive)
  (let ((file-path (substring-no-properties (current-kill 0))))
    (if (string-match-p "\\.\\(png\\|jpe?g\\|gif\\|svg\\)$" file-path)
        (let ((relative-path (file-relative-name file-path)))
          (insert (format "#+ATTR_HTML: :width 300\n[[file:%s]]" relative-path))
          (org-redisplay-inline-images))
      (message "Clipboard content is not a supported image file path. No insertion performed."))))

  ;; npm install -g pyrightが別で必要
  (leaf eglot
    :ensure t
    :defvar eglot-server-programs
    :hook
    (python-ts-mode-hook . eglot-ensure)
    :config
    (with-eval-after-load 'eglot
      (add-to-list 'eglot-server-programs
                   '((python-mode python-ts-mode) .
                     ; ruffコマンド内蔵のruff-lspを使う場合
                     ;("ruff" "server")
                     ; pip install ruff-lspで入れたruff-lspを使う場合
                     ;("ruff-lsp")
                     ("pyright-langserver" "--stdio")
                     ))))

  ;; Tree-sitter
  (use-package treesit-auto
    :custom
    (treesit-auto-install 'prompt)
    :config
    (treesit-auto-add-to-auto-mode-alist 'all)
    (global-treesit-auto-mode))

  ;; code format
  ;; ref https://tam5917.hatenablog.com/entry/2024/07/01/150557
  ;; reformatterについての解説 https://apribase.net/2024/06/10/emacs-reformatter/
  (leaf reformatter
    :ensure t
    :config
    (leaf ruff-format
      :ensure t
      :hook
      (python-ts-mode-hook . ruff-format-on-save-mode))
    (reformatter-define ruff-sort-imports
                        :program "ruff"
                        :args (list "check" "--fix" "--select" "I001" "--stdin-filename"
                                    (or (buffer-file-name) input-file))
                        :group 'python)
    ;; 保存時にruffでimport順をソートするマイナーモード
    (add-hook 'python-ts-mode-hook #'ruff-sort-imports-on-save-mode)
    ;; ruffによってfixをかけるコマンドやマイナーモードを導入
    (reformatter-define ruff-fix
                        :program "ruff"
                        :args  (list "check" "--fix" "--stdin-filename"
                                     (or (buffer-file-name) input-file))
                        :group 'python)
    )

  ;; syntax check
  ;; ref https://mako-note.com/ja/python-emacs-eglot/#pyright
  (use-package flymake-ruff
    :ensure t
    :hook (eglot-managed-mode-hook . (lambda ()
                                       (when (derived-mode-p 'python-mode 'python-ts-mode)
                                         (flymake-ruff-load)))))

  (use-package flymake
    :ensure t
    :bind (nil
           :map flymake-mode-map
           ("C-c C-p" . flymake-goto-prev-error)
           ("C-c C-n" . flymake-goto-next-error))
    :config
    (set-face-background 'flymake-errline "red4")
    (set-face-background 'flymake-warnline "DarkOrange"))

  (use-package flymake-diagnostic-at-point
    :ensure t
    :after flymake
    :config
    (add-hook 'flymake-mode-hook #'flymake-diagnostic-at-point-mode)
    (remove-hook 'flymake-diagnostic-functions 'flymake-proc-legacy-flymake))


  ;; code template, completion
  ;; ref https://mako-note.com/ja/python-emacs-eglot/#tempel
  ;; ref https://qiita.com/nobuyuki86/items/7c65456ad07b555dd67d
  ;; ref https://github.com/minad/tempel

  (use-package corfu
    :custom ((corfu-auto t)
             (corfu-auto-delay 0)
             (corfu-auto-prefix 1)
             (corfu-cycle t)
             (corfu-on-exact-match nil)
             (tab-always-indent 'complete))
    :bind (:map corfu-map
           ("TAB" . corfu-insert)
           ("<tab>" . corfu-insert)
           ("RET" . corfu-insert)
           ("<return>" . corfu-insert))
    :init
    (global-corfu-mode +1))

  ;; Configure Tempel
  (use-package tempel
    ;; Require trigger prefix before template name when completing.
    :custom
    (tempel-trigger-prefix "<")

    :bind (("M-+" . tempel-complete) ;; Alternative tempel-expand
           ("M-*" . tempel-insert))

    :init

    ;; Setup completion at point
    (defun tempel-setup-capf ()
      ;; Add the Tempel Capf to `completion-at-point-functions'.
      ;; `tempel-expand' only triggers on exact matches. Alternatively use
      ;; `tempel-complete' if you want to see all matches, but then you
      ;; should also configure `tempel-trigger-prefix', such that Tempel
      ;; does not trigger too often when you don't expect it. NOTE: We add
      ;; `tempel-expand' *before* the main programming mode Capf, such
      ;; that it will be tried first.
      (setq-local completion-at-point-functions
                  (cons #'tempel-complete
                        completion-at-point-functions)))

    (add-hook 'conf-mode-hook 'tempel-setup-capf)
    (add-hook 'prog-mode-hook 'tempel-setup-capf)
    (add-hook 'text-mode-hook 'tempel-setup-capf)

    ;; Optionally make the Tempel templates available to Abbrev,
    ;; either locally or globally. `expand-abbrev' is bound to C-x '.
    ;; (add-hook 'prog-mode-hook #'tempel-abbrev-mode)
    ;; (global-tempel-abbrev-mode)
  )

  ;; Optional: Add tempel-collection.
  ;; The package is young and doesn't have comprehensive coverage.
  (use-package tempel-collection)


  (leaf *complement
    :config
    (leaf vertico
      :ensure t
      :init
      (vertico-mode))
    (leaf consult
      :ensure t)
    (leaf orderless
      :ensure t
      :init  (icomplete-mode)
      :custom  (completion-styles . '(orderless)))
    (leaf embark
      :ensure t
      :bind
      ("C-c C-o" . embark-export)
      ("C-S-a" . embark-act)       ;; pick some comfortable binding
      ("C-h B" . embark-bindings)) ;; alternative for `describe-bindings'
    (leaf embark-consult
      :ensure t
      :after (embark consult)
      :hook
      (embark-collect-mode . consult-preview-at-point-mode))
    (leaf marginalia
      :ensure t
      :defun marginalia-mode
      :init
      (marginalia-mode))
    (leaf affe
      :ensure t
      :after (orderless consult))
    )

  ;; ref: https://blog.tomoya.dev/posts/emacs-on-local-llm/
  (leaf *llm
    :config  
    (leaf llm
      :ensure t
      :after t
      :require llm-ollama)
    (leaf ellama
      :ensure t
      :custom
      (ellama-language . "Japanese")
      (ellama-naming-scheme . 'ellama-generate-name-by-llm)
      ;(ellama-provider . (make-llm-ollama
      ;		:chat-model "codestral:22b-v0.1-q4_K_S"
      ;			:embedding-model "codestral:22b-v0.1-q4_K_S"))
      ;(ellama-translation-provider . (make-llm-ollama
      ;                                :chat-model "aya:35b-23-q4_K_S"
      ;                                :embedding-model "aya:35b-23-q4_K_S"))
      (ellama-providers . '(("codestral" . (make-llm-ollama
                                            :chat-model "codestral:22b-v0.1-q4_K_S"
                                            :embedding-model "codestral:22b-v0.1-q4_K_S"))
                            ("gemma2" . (make-llm-ollama
                                         :chat-model "gemma2:27b-instruct-q4_K_S"
                                         :embedding-model "gemma2:27b-instruct-q4_K_S"))
                            ("command-r" . (make-llm-ollama
                                            :chat-model "command-r:35b"
                                            :embedding-model "command-r:35b"))
                            ("llama3.1" . (make-llm-ollama
                                           :chat-model "llama3.1"
                                           :embedding-model "llama3.1:8b"))))))

  (setq display-time-interval 60)
  (setq display-time-string-forms
    '((format "%s:%s" 24-hours minutes)))
  (setq display-time-day-and-date t)
  (display-time-mode t)
  (global-display-line-numbers-mode 1)

  (provide 'init)

  (custom-set-faces
   ;; custom-set-faces was added by Custom.
   ;; If you edit it by hand, you could mess it up, so be careful.
   ;; Your init file should contain only one such instance.
   ;; If there is more than one, they won't work right.
   '(default ((t (:family "Courier 10 Pitch" :foundry "bitstream" :slant normal :weight normal :height 143 :width normal)))))

  (global-set-key (kbd "\C-ca") 'org-agenda)
  (setq org-agenda-files '("~/diary/"
                           "~/work/2024/emacs-python/"))


  (global-set-key (kbd "<f6>") 'org-capture)

emacsのビルド

emacs jupyterを使うときに、zmq buildが必要で、snapのemacsだとglibのバージョンが異なるとかで、ビルドしてもzmqがうまく使えない

そのため、snap emacsを消して、emacsをインストールしなおす必要がある。 せっかくなのでemacsもビルドすることにした。

OSはUbutu22を使っている。

$ cd ~
$ mkdir -p .local/work
$ cd .local/work
# imagemagick>7のインストール
# ref: [[https://virment.com/install-latest-imagemagick-and-enable-heic/][ubuntuに最新版のimagemagickをインストールしてheic形式に対応させる手順]]
$ wget https://imagemagick.org/download/imagemagick.tar.gz
$ tar xvzf imagemagick.tar.gz
$ cd imagemagick-7.1.1-38/
$ ./configure 
$ make
$ sudo apt-get install  checkinstall
$ sudo checkinstall
$ cd ..
# emacsのビルド
$ git clone https://github.com/emacs-mirror/emacs.git
$ sudo apt -y install texinfo
# いろいろインストールしすぎたかもしれない。libgccjit-*-devは必要
$ sudo apt install autoconf automake libtool m4 make gettext texinfo gcc g++ m17n-db libm17n-dev libncurses5-dev libgpm-dev xaw3dg-dev libgtk2.0-dev libgtk-3-dev libwebkit2gtk-4.0-dev libxpm-dev libjpeg-dev libgif-dev libtiff-dev librsvg2-dev libmagick++-dev libotf-dev liblcms2-dev libgnutls28-dev libtree-sitter-dev libgccjit-*-dev libselinux1-dev libsystemd-dev libacl1-dev ispell mailutils
$ ./build-emacs.sh

build-emacs.shは 2025年からを生きるためのemacs入門 の記事を参考に29.4のビルド用スクリプトを作成した

#!/usr/bin/env bash

set -euxo pipefail -o posix

today=$(date +'%y%m%d')

cd emacs
git fetch --all
git checkout emacs-29.4
git clean -fdx
cd ..

rm -rf "emacs-29.4-${today}"
cp -r emacs "emacs-29.4-${today}"

cd "emacs-29.4-${today}"
./autogen.sh
./configure --prefix $home/.local --with-tree-sitter --with-xwidgets --with-native-compilation=aot --with-imagemagick
make -j6
make install -j6

echo '=== emacs 29.4 build complete ==='

無事native-compile, tree-sitter, xwidgets, imagemagickが入った

Discussion