目次

= emacs / LM: [2025-01-13 01:28:49]

1. 各種 mode/パッケージ

1.1 migemo

[2023-11-05]

migemo は,emacs 上で日本語をインクリメンタルサーチするための仕組み.デフォルトだと(かな漢字変換の) skk 由来の辞書をベースに,ローマ字を内部的に漢字に変換しつつ,バッファ内を検索する.大変便利.migemo の実装はいくつか存在していて,本メモを書いた時点では cmigemo がメジャーな模様.ruby や python での実装をしている人なども見かけた.検索アルゴリズムや辞書データのありかたなど,弄べる部分が多いに違いない.

利用方法は,まず,OS のネイティブに cmigemo をインストールする.

# FreeBSD
% sudo portmaster -D japanese/cmigemo
# Ubuntu
% sudo apt-get install migemo

emacs 側の設定は,use-package を使うなら以下のような形.起動を速くしたいと言う人のブログを参考にしたので,遅延読み込みを意識した設定になっている.

(use-package migemo
  :ensure t
  :if (executable-find "cmigemo")
  :commands (migemo-init)
  :defer t 
  :config
  (setq migemo-command "cmigemo")
  (setq migemo-options '("-q" "--emacs"))
  (cond
   ((string= os-type "bsd")
    (setq migemo-dictionary "/usr/local/share/cmigemo/utf-8/migemo-dict"))
   ((string= os-type "linux")
    (setq migemo-dictionary "/usr/share/cmigemo/utf-8/migemo-dict"))
   )
  (setq migemo-user-dictionary nil)
  (setq migemo-regex-dictionary nil)
  (setq migemo-coding-system 'utf-8-unix)
  :hook 
  (emacs-startup . migemo-init)
  )

パッケージ管理の仕組みを使わないなら,以下のような形かもしれない.

(require 'migemo)
(setq migemo-command "cmigemo")
(setq migemo-options '("-q" "--emacs"))
(cond
 ((string= os-type "bsd")
  (setq migemo-dictionary "/usr/local/share/cmigemo/utf-8/migemo-dict"))
 ((string= os-type "linux")
  (setq migemo-dictionary "/usr/share/cmigemo/utf-8/migemo-dict"))
 )
(setq migemo-user-dictionary nil)
(setq migemo-regex-dictionary nil)
(setq migemo-coding-system 'utf-8-unix)
(migemo-init)

os-type の部分は,事前に以下のような形で OS を取得している.事前に取得している意味はあまりないので,そのうちリファクタしてしまいたい.

(setq os-type nil)
(cond ((string-match "apple-darwin" system-configuration) ;; Mac
       (setq os-type "mac"))
      ((string-match "linux" system-configuration)        ;; Linux
       (setq os-type "linux"))
      ((string-match "freebsd" system-configuration)      ;; FreeBSD
       (setq os-type "bsd"))
      ((string-match "mingw" system-configuration)        ;; Windows
       (setq os-type "win")))

1.1.1 雑記

[2023-11-06]

そういえば,pure lisp な migemo 実装は存在しないのかな…?軽く調べても見つからなかった.

また,ローマ字変換を利用していると言うことで,例えば漢語のような文字については検索できないのでは…?という気もしている.

検索してたら,情報処理学会の論文を発見.高林さんが最初に開発,というブログは見かけていたが,増井さんがソニーの研究所にいる時の成果の一つなんすね..

1.2 vc

[2023-11-05]

<TBD>

バージョンコントールのための統合的な機能.svn も扱えている気がする.

キーバインド コマンド 説明 Subversionのコマンド
C-x v = vc-diff 差分を表示 diff
C-x v l vc-print-log 履歴を表示 log
C-x v g vc-annotate 注釈を表示 blame
C-x v ~ vc-revision-other-window 過去のバージョンを表示 cat
C-x v + vc-update 更新 update
C-x v v vc-next-action コミット commit
C-x v i vc-register ファイルの追加 add
C-x v u vc-revert 修正の破棄 revert
C-x v d vc-dir 状態の表示 status
ediff-revision Ediffで差分を表示

メモ: C-x v v すると,コメント入力を求められる.入力が終わったら,C-c C-c したら commit された.

参考:

1.2.1 vc-svn 利用時の svn コマンドへの引数指定

[2023-11-06]

どうしても svn コマンドに引数を渡して実行したいケースがある.ドキュメントには書いてなかったが,vc-svn.el の中に,以下のような変数が設定されているのを発見した.

;; Might be nice if svn defaulted to non-interactive if stdin not tty.
;; https://svn.haxx.se/dev/archive-2008-05/0762.shtml
;; https://svn.haxx.se/dev/archive-2009-04/0094.shtml
;; Maybe newer ones do?
(defcustom vc-svn-global-switches (unless (eq system-type 'darwin) ; bug#13513
                                    '("--non-interactive"))
  "Global switches to pass to any SVN command.
The option \"--non-interactive\" is often needed to prevent SVN
hanging while prompting for authorization."
  :type '(choice (const :tag "None" nil)
		 (string :tag "Argument String")
		 (repeat :tag "Argument List"
			 :value ("")
			 string))
  :version "24.4")

良く分からないが,MacOS 以外では –non-intereractive という引数を svn コマンドに渡している模様.リストになっているので,以下のように add-to-list すればコマンドライン引数を追加できた.

(use-package vc
  :after vc-svn
  :config
  (add-to-list 'vc-svn-global-switches "--hogehoge=hogehoge"))

1.3 recentf

[2023-11-05]

今まで開いたことのあるファイル一覧を保存しておいてくれる機能.emacs 25 くらい?からは,mode を有効するのに,その mode にたいして non-nil を指定すれば良いっぽい.

(recentf-mode 1)

保存情報は,~/.emacs.d/recentf ファイルに以下のように保存されている.

;;; Automatically generated by ‘recentf’ on Thu Nov  2 15:57:51 2023.
 
(setq recentf-list
      '(
        "/mnt/c/Users/skk/svn/.elisp/.emacs-29"
        "/mnt/c/Users/skk/svn/howm/2023/10/2023-10-19-200323.howm"
        ))
 
(setq recentf-filter-changer-current 'nil)
 
^L
;; Local Variables:
;; coding: utf-8-emacs
;; End:

保存しておくパスの件数は以下のように設定する.

(setq recentf-max-menu-items 100)

保存しておいて欲しくないファイルについては,以下のように exclude リストを recentf-exclude に追加する.

(add-to-list 'recentf-exclude
	     (recentf-expand-file-name "\\.\\*AppData/Local/Temp/\\.\\*"))

emacs を起動している間は,除外したファイルも一覧に入っているが,起動時(?)に (recentf-cleanup) が呼ばれて,ファイル内から指定されたファイルが除外される模様.

recentf-cleanup で,M-x describe-function したら以下のように書いてあった.

Cleanup the recent list.
That is, remove duplicates, non-kept, and excluded files.

[2023-12-13]

recentf が増えてくると,検索したくなる.そのような時は,M-x recentf-open-files と打つと,今までの履歴が一覧で出てきて,C-s などで検索して探しやすくなる.

参考:

1.4 color theme

[2023-11-05]

markdown モードのコメント,org の階層,など,一つ一つの要素の色を設定していくのはとても大変なので,一気に色の指定をしてくれる色のセット.

いつから使えるようになったのか分からないが,https://www.nongnu.org/color-theme/ ここら辺を見ていると,2009 年頃にはある程度使われるようになってきていたように思われる.

検索してみると,https://emacsthemes.com/ ここのサイトに情報が多く載っている模様.Vim と同じ配色の Zenburn theme というものが Top Themes を見ると一位にランクしている.

M-x package-list-pagckages から該当の theme をダウンロードし,(load-theme 'abyss-theme t)としても良いし,以下のように use-package を利用してもよい.僕は,.emacs 内にいろいろな残骸が残っているので,after-init-hook で load-theme しているが,余計な設定が無い人ならば,単に :config の中で load-theme するだけで動作すると思われる.

(use-package 
  se-package
  ;; darktooth-theme
  abyss-theme
  ;; darkmine-theme
  ;; doom-themes
  :ensure t
  :defer nil
  :if (window-system)
  :config
  ;; 多分 after-init の前に色々設定してるから,終わってから更に上書きする.
  (add-hook 'after-init-hook 
  	(lambda ()
  	  (load-theme 'abyss t)))))

1.5 use-package

1.5.1 概要

[2023-11-01] 2000 年中盤ころはまだ,emacs でパッケージ管理という概念はあんまりなかった気がする.そういえば,最近は新しい機能を入れる時に M-x packge-list-packages してるな,と思ったので調べてみたら,パッケージ管理機能がかなり充実してきているのに気づいた.

少しぐぐってみると,leaf.el というのが流行っているのに気づいたのだが,検索している時に英語の情報が全然出てこないので,英語だけで調べてみたところ,leaf.el は全然出てこなかった.一度設定すると,下手すると 10 年単位で放置する可能性があるので,単に流行ってるだけのものは使いたくない.leaf.el ってなんなんだ?と調べてみたところ,日本人が作っている use-package のラッパーみたいなものだということが分かった.(なるほど,だから日本語の情報が大量にあるのか...)英語方面で調べてみると use-package + straight というのが良く引っかかる.straight というのはまだ良く分かってないが,少なくとも use-package を使うと,

  • パッケージが入ってなかったら勝手に入れてくれる(emacs デフォルトのパッケージの設定も同じ記法で管理可能)
  • setq で指定していたパッケージ毎の設定を見やすく記述できる

という利点があることが分かった.また,登場してからすでに 10 年近く経っているのに,まだ現役で設定している人がいたので,まだまだ寿命が長そうだな,ということで,.emacs の色々を use-package にしてみることにした.

use-package は,package.el のマクロということで,package.el を使い倒すための仕組みらしい.

1.5.2 初期設定

まず,package でオンラインで取得できる設定をした後に,use-package が入ってなくてもインストールされるようにする.

;------------------------------------------------------------------------
; melpa 設定 (package manager)
;------------------------------------------------------------------------
(require 'package)
(add-to-list 'package-archives '("melpa" . "http://melpa.org/packages/") t)
(add-to-list 'package-archives '("org" . "https://orgmode.org/elpa/") t)
;; (package-initialize) ;; from version 27, no need. 
 
;;------------------------------------------------------------------------
;; use-package 設定
;;------------------------------------------------------------------------
 
;; もし,use-package が入ってなかったら,インストールする.
;; これ以外は,use-package を使って自動でインストールして貰う.
(unless (package-installed-p 'use-package)
  (package-install 'use-package))

1.5.3 use-package 基本利用方法

最も良いドキュメントは,use-package の github の README.md になる.

基本の使いかたは,以下のように書くことで,(require 'package-name) と同様のことをする.

(use-package package-name) 

require と load は少し意味が違っていて,require は遅延ロードで,load は即座にロードする,という意味らしい.遅延ロードにすることで,emacs の起動が早くなり,利用する際に該当のパッケージの本体が読み込まれることになる模様.ここの理解はまだ浅いので,そのうち調べたい.

例えば,emacs-lisp-mode を利用する際に hook でいくつかの mode を起動する場合,use-package を使わないと,

(add-hook 'emacs-lisp-mode-hook 'action-lock-mode)
(add-hook 'emacs-lisp-mode-hook 'auto-complete-mode) 

のように書いていたが,use-package に直すと,

(use-package emacs-lisp-mode
  :hook
  (emacs-lisp-mode . action-lock-mode)
  (emacs-lisp-mode . auto-complete-mode))

となる.:hook を利用する場合,emacs-lisp-mode-hook のように -hook がついていたが,:hook を利用する場合は,-hook は除外して記述してくれ,とのこと.

:hook という記述方法は,どうも cl-lib で提供される common-lisp の記述方法のようだが,これも良く分かっていない.そのうち調べたい.

use-package には,:hook のように事前に設定する機能がたくさんある.

1.5.4 atomic-chrome 設定の例

[2023-11-05]

ここでは,色々設定している僕の atomic-chrome を例に載せておく.

(use-package atomic-chrome
  :ensure t
  :after action-lock ;; howm ;; howm を起動するまで atomic-chrome が起
		     ;; 動しないことになってしまう?action-lock は事前
		     ;; に起動されている.
  :if (window-system)
  :init
  (setq atomic-chrome-default-major-mode 'org-mode) ;; org-mode での編
						    ;; 集をデフォルト
						    ;; にする
  (setq atomic-chrome-buffer-open-style 'full) ;; split is default. 
  (setq debug-on-error t)
  (setq winactivate nil)
  ;; URL 毎の編集 mode 設定
  (setq atomic-chrome-url-major-mode-alist
	'(("redmine\\.cho-textbook\\.jp" . markdown-mode)
	  ("app\\.slack\\.com" . slack-text-mode)))
  :bind (:map atomic-chrome-edit-mode-map
	      ("C-c d" . erase-buffer))
  :hook
  ((after-init . atomic-chrome-start-server)
   (atomic-chrome-edit-mode . turn-off-auto-fill))
  :config
  ;; 編集開始時の hook
  (add-hook
   'atomic-chrome-edit-mode-hook
   (lambda ()
     ;; (atomic-chrome-edit-mode . turn-off-auto-fill)
     (setq winactivate (shell-command-to-string "focusWslEmacs3.exe"))
     ))
  ;; 編集終了時の hook
  (add-hook
   'atomic-chrome-edit-done-hook
   (lambda ()
     (my-atomic-chrome-send-buffer-text)
     (setq windeactivate
	   (shell-command-to-string (format "back_to_win.exe %s" winactivate)))
     ))
  ;;(atomic-chrome-start-server)
  )

:ensure は,そのパッケージがインストールされているかを確認する.t だと package から自動でインストールしてくれる.

:after は指定されたパッケージが require されたあとにロードされるようにする.

:if は,そこで指定された式や関数が non-nil を返したら読み込みをする,というもの.ここでは,(window-system) が何らか指定されていれば,ということになる.WSL の場合は,x11 で起動しているので x と返ってくる.Windows や Mac ネイティブな emacs だとどうなるのかは調べてない.

:init は,ロード前に指定しておく命令を記述する.ここでは,atomic-chrome の初期設定で必要ないくつかの変数に値を指定している.

:bind は,キーマップを指定する.特に,:map で指定する時は,このモード専用のキーマップに指定するので,このモードだけのキーマップが設定できる.

:hook は前述の通り,hook を指定する.atomic-chrome に関係する hook でなくともここに記載しておけば良い.

:config は,ロード後に指定する命令を記述する.:init と分けて考えるように.

日本語では,以下の Qiita の記事がある程度まとめてくれているので,読みやすい.

https://qiita.com/kai2nenobu/items/5dfae3767514584f5220

1.6 scratch-log [2023-10-20]

*scratch* バッファに色々書く人は多いと思う.そして大事なことを書いたまま,emacs を閉じてしまい,メモが消えたことがある人も多いと思う.

そういう人のために,*scratch* バッファに書いた情報を自動保存しておいてくれる scratch-log.el というものがある.設定しておけば,数十秒毎など,指定した時間毎にバッファの内容を保存してくれる.

インストールは,M-x package-list-packages から.

設定は,例えば以下のような形.

;;; scratchのログ、直前の内容
(setq sl-scratch-log-file "~/.emacs.d/.scratch-log")
(setq sl-prev-scratch-string-file "~/.emacs.d/.scratch-log-prev")
(setq sl-restore-scratch-p t)           ;復元
(setq sl-prohibit-kill-scratch-buffer-p t) ;削除不能
;; *scratch*とscratch-logのメジャーモードをorg-modeにする
;; initial-major-mode は,after-init-hook の後に設定される.
(setq initial-major-mode 'org-mode)
(add-to-list 'auto-mode-alist '("scratch-log" . org-mode))
;;; 30秒ごとに自動保存
(setq sl-use-timer t)
(setq sl-timer-interval 3)
;;; requireした時点で各種フック・タイマーが設定される
 
(require 'scratch-log)
 
;;(add-hook 'after-init-hook 'my-scratch-func)
(add-hook 'emacs-startup-hook 'my-scratch-func)
(defun my-scratch-func ()
  (with-current-buffer "*scratch*"
    (message "my-scratch-func")
    (highlight-lines-matching-regexp "^>" "hi-blue")))

参考サイトから更に,*scratch* バッファのデフォルトモードを設定し,メールのように行頭に > がある場合に色を付ける設定を付け加えた.

どのタイミングで org-mode の指定や,ハイライト指定をすべきかは,起動時シーケンス についてを確認のこと.

参考:

ちなみに,emacs 29 で,scratch-buffer という新たな命令が出来て,間違って消しても *scratch* バッファを作れるなんてのは見かけたが,書いた内容が復活しないのかな?と思いつつ,まだちゃんと確認はしていない.

1.7 time-stamp [2023-10-19]

ファイルを保存した際に,ファイル内の特定の場所に保存時間を書いておきたくなった.howm-mode 利用時に,1 行目に書いておけば,一覧の中で更新日時が見られるので便利.

奥村先生のページにほぼ解答が載っていた.

(add-hook 'before-save-hook 'time-stamp)
(setq time-stamp-pattern "2/LM:[ \t]+\\\[%:y-%02m-%02d %02H:%02M:%02S\\\]")

奥村先生の方は,Last modified: <time>…</time> という記載方法だったが,これだと長過ぎるし,<time> というタグである必要はないため,LM: […] という記載になるようにしている.また,JST という記載もいらないので,%Z も消した.2/ は,ファイルの2行目までを検索する,という意味.-8/ と書くとファイルの末尾から 8 行を探すようになる模様.

1.8 atomic-chrome

1.8.1 概要 [2023-10-12]

slack や redmine など,最近は Web 上でテキスト入力を求めてくることが多い.だが,文字は emacs で入力したい.

各サービスで公開されている API を,認証コードを取得し利用するのも一つの方法で,例えば emacs-slack などを利用すれば emacs 内で完結するしとても大変嬉しいが,サービス毎に設定しなければならないし,lisp package が存在しないケースももちろんある.

atomic-chrome は,<textarea> の中身を emacs で編集するためのモードである.デフォルト設定では Ctrl-Shift-k を押すと emacs 内にテキストが飛び,入力内容がリアルタイムで textarea 内に反映される.

1.8.2 インストール

  • GhostText(https://github.com/GhostText/GhostText) を chrome にインストールする.
  • atomic-chrome を M-x package-list-packages から探し,インストールする.
  • .emacs に以下の設定をする.
    (require 'atomic-chrome)
    (atomic-chrome-start-server)
    (setq atomic-chrome-default-major-mode 'org-mode)
     
    (add-hook 'atomic-chrome-edit-mode-hook
    	  (lambda ()
    	    (turn-off-auto-fill)
    	    ))
     
    (setq atomic-chrome-url-major-mode-alist
          '(("redmine\\.cho-textbook\\.jp" . markdown-mode)))
     
    (setq atomic-chrome-buffer-open-style 'full) ;; split is default. 

デフォルトだと auto-fill が有効だったので,無効にするように hook の設定をしている.必要なければ,add-hook 部分を削除すれば良い.

特定のサイトにおいて,minor mode を変更したい場合,atomic-chrome-url-majo-mode-alist に追加しておけば良い.

atomic-chrome-buffer-open-style は,デフォルトで split が設定されており,window の半分くらいのサイズの frame に表示される.full にしておくと window 全体に表示されるので見やすいと思う.

1.8.3 使いかた

ブラウザ上の textarea 領域で,右クリック→「Activate GhostText on field」を選択するか,Ctrl-Shift-k とすると,emacs 内に編集用 buffer が登場する.

1.8.4 emacs のウィンドウを動的にアクティブにする [2023-10-15]

Chrome 内の textarea の編集を emacs に飛ばせるということで大変便利な atomic-chrome だが,飛ばした後に emacs を探すのがめんどくさい.また,編集が終わって C-c C-c とした後に,元の Chrome を探すのがめんどくさい.

Windows + WSL 環境において,Chrome から emacs に飛ばした際,emacs が activate され,編集が終わったら,Chrome が activate されてくれると,編集に関して一度もマウスを触ることがなくなるため,大変効率的である.

Window をアクティブにする方法を探していたところ,AutoHotKey が便利そうというのが分かった.このツールの Ahk2Exe を利用して単一コマンドを作成し,atomic-mode に入ったら,emacs の Window をアクティブにし,終わったら呼出し元をアクティブにできれば良い.

AutoHotKey をインストールした人は必要無いのかもしれないが,zip 版を持ってきた人は,初めて Ahk2exe を利用する前に Ahk2exe.exe を起動し,Base File を選択し,Save ボタンを押さないと,no default base file specified のようなエラーが出て,コンパイルが出来ないので注意.

AutoHotKey の利用方法については,AutoHotKey Wikiに日本語の情報が良くまとまっているので,参照するとよいと思う.

まず,Chrome → emacs については,以下のような AutoHotKey スクリプト (ahkファイル) を用意する.

focusWslEmacs1.ahk
;; ウィンドウタイトルを部分一致で検索
SetTitleMatchMode, 2
;; Focus to Emacs
WinActivate, Emacs ahk_exe vcxsrv.exe

これは,Emacs とタイトルのついている Window を探し,アクティベート(=全面に持ってくる)スクリプトになる.AutoHotKey 附属の Ahk2Exe.exe を利用し,以下のようにコンパイルする.

PS C:\Users\skk\software\autohotkey> .\Compiler\Ahk2Exe.exe /in .\focusWslEmacs1.ahk /out focusWslEmacs.exe

出来上がった,focusWslEmacs.exe を例えば,$HOME/bin/ 以下のような,WSL のシェルから見てパスの通ったところに置く.WSL 環境の場合,exe ファイルも WSL のシェルから実行できるので,混ざってもいいから置いてしまう.

そして以下のように,atomic-mode に入った際の hook で,呼び出されるようにする.

(add-hook 
 'atomic-chrome-edit-mode-hook
 (lambda ()
   (turn-off-auto-fill)
   (shell-command-to-string "focusWslEmacs.exe")
   (message (format "winactivate message is %s" winactivate))
   ))

この状態で,hook を再評価するか emacs を再起動して,chrome の textarea より Ctrl-Shift-k すると,emacs が全面に出てくるはず.

ただしこの状態だと,C-c C-c で編集終了しても,Chrome に戻れない. 編集終了時に Chrome に戻るには,呼出し元の Chrome の Window 情報が必要となる. 情報を得るためのコードとして,以下のようなものを見付けた.

focusWslEmacs2.ahk
;; ウィンドウタイトルを部分一致で検索
SetTitleMatchMode, 2
;; Focus to Emacs
WinActivate, Emacs ahk_exe vcxsrv.exe
 
Focus(z){
   static past ; saves the last z parameter passed
   windows := AltTabWindows()
 
   if (z = "next") {
      static next, count
 
      if (z != past || next > windows.length()) {
         loop % windows.length()
            next := A_Index+1
         until (WinActive("A") = windows[A_Index])
         past := z
         WinActivate, % "ahk_id" windows[next]
         return windows[next]
      } else if (next < windows.length()) {
         next++, count++
         past := z
         WinActivate, % "ahk_id" windows[next]
         return windows[next]
      } else if (next = windows.length()) {
         count--
         past := z
         if (count = 0)
            past := "reset"
         WinActivate, % "ahk_id" windows[next]
         return windows[next]
      }
   }
 
   if (z = "recent") {
      loop % windows.length()
         recent := A_Index+1
      until (WinActive("A") == windows[A_Index])
      past := z
      return windows[recent]
 
      ; WinActivate, % "ahk_id" windows[recent]
      ; return windows[recent] 
 
   }
 
   if (z ~= "(?i)^bot") {
      past := z
      WinActivate, % "ahk_id" windows[windows.length()]
      return windows[windows.length()]
   }
 
   if (z is integer) {
      past := z
      WinActivate, % "ahk_id" windows[z]
      return windows[z]
   }
}
 
AltTabWindows() { ; modernized, original by ophthalmos https://www.autohotkey.com/boards/viewtopic.php?t=13288
   static WS_EX_APPWINDOW :=      0x40000 ; has a taskbar button
   static WS_EX_TOOLWINDOW :=        0x80 ; does not appear on the Alt-Tab list
   static GW_OWNER :=                   4 ; identifies as the owner window
 
   AltTabList := {}
   windowList := ""
   DetectHiddenWindows, Off ; makes DllCall("IsWindowVisible") unnecessary
   WinGet, windowList, List ; gather a list of running programs
 
   Loop, %windowList% {
      ownerID := windowID := windowList%A_Index% + 0 ; format as an unsigned integer
      Loop ; gets the hWnd of the top-most parent if the window is a child window.
         ownerID := DllCall("GetWindow", "uint", ownerID, "uint", GW_OWNER)
      Until !DllCall("GetWindow", "uint", ownerID, "uint", GW_OWNER)
      ownerID := ownerID ? ownerID : windowID
 
      If (DllCall("GetLastActivePopup", "uint", ownerID) = windowID) {
         WinGet, es, ExStyle, ahk_id %windowID%
         ; Must appear on the Alt+Tab list, have a taskbar button, and not be a Windows 10 background app.
         If (!((es & WS_EX_TOOLWINDOW) && !(es & WS_EX_APPWINDOW)) && !IsInvisibleWin10BackgroundAppWindow(windowID))
            AltTabList.Push(Format("0x{:x}", windowID))
      }
   }
 
   return AltTabList
}
 
IsInvisibleWin10BackgroundAppWindow(hWindow) {
   result := 0
   VarSetCapacity(cloakedVal, A_PtrSize) ; DWMWA_CLOAKED := 14
   hr := DllCall("DwmApi\DwmGetWindowAttribute", "ptr", hWindow, "uint", 14, "ptr", &cloakedVal, "uint", A_PtrSize)
   if !hr ; returns S_OK (which is zero) on success. Otherwise, it returns an HRESULT error code
      result := NumGet(cloakedVal) ; omitting the "&" performs better
   return result ? true : false
}
 
/*
DWMWA_CLOAKED: If the window is cloaked, the following values explain why:
1  The window was cloaked by its owner application (DWM_CLOAKED_APP)
2  The window was cloaked by the Shell (DWM_CLOAKED_SHELL)
4  The cloak value was inherited from its owner window (DWM_CLOAKED_INHERITED)
*/
 
 
out := 0 
stdout := FileOpen("*", "w", "UTF-8")
stdout.WriteLine(Focus("recent"))
FileAppend % var,  * ; StdOut
stdout.Close()
 
ExitApp % out

AutoHotKey でも,アクティブだった Window のリストというのは簡単には取得できないようで,AltTabWindows() の中でそれを作成している模様.この AltTabWindows() は返り値は返さず,windowList という変数にリストの形で Window ID を保存している.AutoHotKey では,リストにアクセスするには,変数名1 のように,変数名の後ろに数字をつけるだけのようなので,上記スクリプトでは 6 番目の要素にアクセスしている.ちなみに,なぜ6で動作しているのか全然理解できていない.Windows および AutoHotKey に詳しい方がここを見てたら,是非教えて欲しい.[2023-10-19] 追記:やはり,10〜20% くらいの確率で,元の Window に戻れない時が発生する.単に 6 という固定値だと何か間違ってる気がする…

[2023-10-22] 追記:当初のスクリプトは間違ってた気がする.ちゃんと一つ前にアクティブだった Window を取得できるスクリプトが,こちら に載っていたので,修正した.ちなみに,リンク先のスクリプトは v1 向けなので,v2 を使う場合は,その少し下のスクリプトにした方がいいのかもしれない.[2023-10-22] のスクリプトでは,AltTabWindows() から window を選ぶための Focus() という関数が定義されている.この関数では,選んだ window に飛んで(WinActivate)しまうので,僕は,“recent” 部分で window ID を return するようにしている.多分,recent 以外の if 部分は使わないし消してしまっていいのだが,将来何かしたくなるかも?ということで残しておく.消したい人は消した方が良いと思う.

このスクリプトでは,標準出力に Window ID を出力している.僕の環境の PowerShell で実行してもどうやっても出力されなかったが,emacs から実行しみると ID が取得できているのでヨシとする.ここも,詳しい方がいたら是非教えて欲しい.上記スクリプトの FileAppend だの stdout.Close() だのは,調べてる中で flush した方が良いと書かれていたので試した残骸だが,結局うまく動作していない.

この一つ前の ID を使い,編集終了時に,元の Chrome に戻るための AutoHotKey プログラムが必要になる.これは以下のように 1 行の ahk スクリプトで良い.

back.ahk
WinActivate, ahk_id %1%

%1% は,コマンドライン引数を表す.%0% だと引数の数だった気がする.

必要な ahk が揃ってきたので,先ほどと同じように,コンパイルする.

PS C:\Users\skk\software\autohotkey> .\Compiler\Ahk2Exe.exe /in .\focusWslEmacs2.ahk /out focusWslEmacs.exe
PS C:\Users\skk\software\autohotkey> .\Compiler\Ahk2Exe.exe /in .\back.ahk /out back_to_win.exe

exe はそれぞれ $HOME/bin/ 以下にコピーし,以下のように,hook に登録する.

(add-hook 
 'atomic-chrome-edit-mode-hook
 (lambda ()
   (turn-off-auto-fill)
   (setq winactivate (shell-command-to-string "focusWslEmacs.exe"))
   (message (format "winactivate message is %s" winactivate))
   ))
 
(add-hook 
 'atomic-chrome-edit-done-hook
 (lambda ()
   (message "atomic-chrome-edit-done-hook")
   (setq windeactivate (shell-command-to-string (format "back_to_win.exe %s" winactivate)))))

focusWslEmacs.exe が出力する,一つ前にアクティブだった Window ID,つまり Chrome の ID は,winactive 変数に格納されている.

これで,Chrome と emacs の間での Window 移動の際に,マウスを使わなくて済むはずである.

1.8.5 slack での atomic-chrome

[2023-10-15]

ウィンドウの設定を行なうと,atomic-chrome の使い勝手がかなり向上するが,slack で利用することを考えると,emacs 内から submit できるとチャットの効率が大変あがるので便利な気がする.

atomic-chrome は,GhostText のフロントエンドみたいなものなので,atomic-chrome の機能ではなく,GhostText 側で submit に対応してくれていれば,そのうち emacs からでも対応できそうである.

ということで,GhostText 側を確認してみたが,2013 年に似たようなことを要望した人がいたらしく,作者にめんどくさいとはねられていたが (#44),10 年近く経っているので,もう一度要望を投げてみた (#298).

僕が投げた前後で,作者により秒で却下されてる issue があったが,現段階で 2 日経っても却下されてないので,少し考えてくれてるんじゃないかと期待して気長に待ってみようかと思う.

[2023-11-15]

いちいち slack のウィンドウに戻るのが耐えられなかったので,slack にたいしての超アドホック対応を行なってみた. 大まかには,chrome を remote-debugging mode で起動し,websocket 越しに,今編集している textarea へ Enter のキーイベントを javscript 実行により送る. chrome の remote debugging mode は,localhost からの接続しか設定できないため,WSL2 emacs から Windows の chrome の間では,この方法は実現不可能となる.WSL1 の場合,Linux コマンドも Windows と同じユーザランドで動作しているため,通信できる.

まず,chrome を debug モードで起動させる.Windows ならショートカットのプロパティなどで,–debugging-port=9222 を指定する.

"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" --remote-debugging-port=9222

次に,emacs 上で,chrome.el を動かせるようにする.chrome.el は chrome と remote debugging mode 経由で通信して,タブ一覧を取得するものである.今回は,chrome.el を chrome から返ってくるタブ情報の json のパーサーのように扱う.

次に,以下のコードが動かせるようにする.ちょうアドホックなので,このままでは多分動きません.「テキストを HTML 化しておく」と書いてある部分はまるごとなくても良いと思います.以下の2つの関数は,それぞれ別のオリジナル関数.

  • (atomic-chrome-slack-send-buffer-modify) は,textarea 内のテキストにリストなどがあったら,対応する HTML に変化する関数
  • (my-atomic-chrome-send-buffer-text) atomic-chrome の中にある関数をラップしてるオリジナル関数.単に ghost-script の方に最新テキストを送るために呼び出している.
(defun skk/atomic-chrome-slack-submit-message ()
  (interactive)
  ;; もし *chrome-tabs* バッファの中で chrome.el が動いてなかったら,バッファ内で動作させる
  (setq edit-buffer (current-buffer))
  (chrome)
  ;; 自分のバッファに戻ってくる
  (switch-to-buffer edit-buffer)
 
  ;; 作業バッファを *chrome-tabs* にする
  (set-buffer "*chrome-tabs*")
 
  ;; *chrome-tabs* バッファ内のタブリストをアップデートする
  (chrome-retrieve-tabs)
 
  ;; url が同じタブを見つける
  ;; ~atomic-chrome 側で取得している url をここまで持ってくる~
  (setq tmp chrome--cached-tabs)
  (setq keys ())
  (maphash (lambda (key value)
             (if (string-match "app.slack.com" (chrome-tab-url (gethash key tmp)))
                 (setq keys (cons key keys)))
             );lambda 
           tmp)
 
  (message (format "num of slack chrome tab: %s" (length keys)))
  ;; assuming only 1 app.slack.com exists
  (setq value (gethash (car keys) tmp))
 
  ;; テキストを HTML 化しておく
  ;; XXX [2023-11-11] save-current-buffer を利用しなければならないはず.
  (set-buffer edit-buffer)
  (setq tmp (atomic-chrome-slack-send-buffer-modify (buffer-substring-no-properties (point-min) (point-max))))
  (message (format "edited text %s" tmp))
  (set-buffer edit-buffer)
  (erase-buffer)
  (insert tmp)
  (my-atomic-chrome-send-buffer-text) 
 
  ;; タブに対して websocket を貼る
  ;;(require 'websocket) ; 多分要らない
  (setq skk/websocket
        (websocket-open (concat
                         "ws://127.0.0.1:9222/devtools/page/"
                         (chrome-tab-id value))
                        :on-message (lambda (_websocket frame)
                                      (message "ws frame: %S"
                                               (websocket-frame-text frame)))
                        :on-error (lambda (_websocket frame)
                                    ;;(message "websocket error: %S" _websocket)
                                    (message "error: %s" (websocket-frame-text frame))
                                             )
                        :on-close   (lambda (_websocket)
                                      (message "websocket closed")
                                      )
 
                        ))
 
  ;; Enter を送信する js コードを送り込んで実行する
  (setq js-code "var hoge = document.getElementsByClassName(\\\"ql-editor\\\");
for (i=0; hoge.length>i; i++) { 
    if(hoge[i].innerHTML.length != 0){
        var push_hoge = hoge[i]; 
    }
}
push_hoge.dispatchEvent(new KeyboardEvent(\\\"keydown\\\", {
    key: \\\"Enter\\\", 
    keyCode: 13, 
  })); 
")
 
  ;; XXX [2023-11-11] json-encode を使うべき.
  ;; (json-encode '(("id" . "10")
  ;;     ("method" . "Runtime.evaluate")
  ;;     ("params" . (("expression" "(\"hoge\")")))))
  (setq json-message (concat "{\"id\":1,\"method\":\"Runtime.evaluate\",\"params\":{\"expression\":\"" js-code "\"}}"))
 
  (sleep-for 0.1) ;; XXX WHY!?!?
  ;;(message json-message)
  (websocket-send-text skk/websocket json-message)
 
  (websocket-close skk/websocket)
 
  ;; 送信したら,buffer 内をきれいにする
  (set-buffer edit-buffer)
  (erase-buffer)
 
  )

取り急ぎ,この関数を Ctrl-Enter に bind しておくと,なんとなく emacs 側からメッセージ送信ができている.できれば Ghost Script 側から,どの textarea 要素を編集しているのかの正確な情報が得られるなら,Enter のキーイベントを送りやすいし,JS コードを受けとってくれるなら,わざわざ remote-debugging を有効にしなくて良いので大変助かる,,,んだけどなあ.

参考:

1.9 howm-mode

1.9.1 概要 [2023-10-04]

「一人お手軽 Wiki もどき」というコンセプトで,emacs 内でテキストドキュメントを管理する為の elisp. 以下のような形で,テキストメモを一覧にしておける.メモは特定のフォルダ内に新規作成されていく.howm 起動時に,特定フォルダ内を grep して,メモの一行目を以下のように表示してくれる.僕は編集時に org-mode と併用して利用している.

 = <<< % menu %
 %"c"[新規] %"D"[複製] 検索(%"s"[固定] %"g"[正規] %"m"[roma]) %":"[昨日] %"."[今日]
 %"a"[一覧] %"l"[最近] %"A"[前後] %"h"[履歴] %"y"[予定] %"t"[Todo] %"w"[酔歩] [全消] [設定]
 %"K"[題↑] [名↑] %"d"[日↓] %"T"[時↓] %"i"[鍵↓] %"r"[更新] %"R"[menu 更新] [menu 編集]
-------------------------------------
最近のメモ
> emacs             | = emacs
> 2013-09-19-154103 | = SFC 授業 / SFC Lecture 
> 2013-09-21-134220 | = BPS 社内
> 2013-09-16-015457 | = emacs in howm 

1.9.2 インストール

M-x package-list-packages から howm をインストールするのが良い.

1.9.3 設定

1.9.3.1 初期設定

(setq howm-directory "/home/skk/svn/howm")
(setq howm-file-name-format "%Y/%m/%Y-%m-%d-%H%M%S.howm")
(add-to-list 'auto-mode-alist '("\\.howm$" . org-mode))
(global-set-key "\C-cqq" 'howm-menu)
(autoload 'howm-menu "howm" "Hitori" t)

上記設定をした後,M-x howm-menu を呼び出せば良い.howm のマニュアルには,\C-c ,, で howm-menu が呼び出せる,と書いてあるが,org-mode が読み込まれているバッファだと,\C-c ,org の機能に割り当てられている為,ここでは \C-c qqに変更している.

1.9.3.2 検索除外

howm の保存ディレクトリにゴミファイルを起きたくなる時がある.そのような時の為に,以下の設定をしておけば,howm-menu呼出し時に,検索されないようにできる.

(setq howm-excluded-file-regexp
      "\\.\\(jpg\\|JPG\\|pptx\\|eps\\|pdf\\|dvi\\|tex\\|toc\\|png\\|PNG\\|svg\\)$")

1.9.3.3 dokuwiki との連携

dokuwiki の記事をローカルに保存,howm 管理にしておく.

<TBD>

1.10 dokuwiki 関係

1.10.1 dokuwiki-mode [2023-09-10]

emacs で dokuwiki を編集する際に,ロードしておけばきれい.outline-magic と連携してちょっと便利.ただし,org-mode ほどの編集サポート力はない.

https://github.com/kai2nenobu/emacs-dokuwiki-mode

ファイルとして保存した場合,以下の設定により,ファイルを開いた際に dokuwiki-mode をロードできる.ついでに,auto-fill mode は off にしておく.

;; .dwiki 拡張子があったら,dokuwiki-mode を起動
(add-to-list 'auto-mode-alist '("\\.dwiki$" . dokuwiki-mode))
(add-hook 'dokuwiki-mode-hook 'turn-off-auto-fill)

1.10.1.1 キーマップ

説明は省略.github の dokuwiki-mode.el の中からコピーしてきた.

    (define-key map (kbd "C-c C-n") 'outline-next-visible-heading)
    (define-key map (kbd "C-c C-p") 'outline-previous-visible-heading)
    (define-key map (kbd "C-c C-f") 'outline-forward-same-level)
    (define-key map (kbd "C-c C-b") 'outline-backward-same-level)
    (define-key map (kbd "C-c C-u") 'outline-up-heading)
    (define-key map (kbd "C-c C-@") 'outline-mark-subtree)

1.10.1.2 backup file を作成しないようにする [2023-09-12]

原因が分からないが,howm-mode 内で .dwiki ファイルを扱ってる際に,backup file (~で終わるファイル)が作成されるようになった.make-backup-files 変数に nil を設定すれば作成されないようだが,howm-mode の時にだけバックアップファイルを作成しないよう,マニュアルを参考にしつつ,以下のようにした.

;; howm-mode の時にはバックアップファイルを作らない
(add-hook 'howm-mode-hook
	  (lambda () (setq-local make-backup-files nil)))

1.10.2 dokuwiki [2022-10] ごろ

dokuwiki の XML-RPC を利用して,emacs で編集した内容を dokuwiki に送信する.動作は安定しており,本 dokuwiki の記事はほぼこの機能でアップデートされている.

https://github.com/accidentalrebel/emacs-dokuwiki

1.10.2.1 インストール

M-x package-list-packages から,選んでインストールするだけ.

dokuwiki 側の設定で,管理 → サイト設定 → 認証 と進み,“remote” の項目にチェックを入れ,“remoteuser” にアクセスするユーザを指定しておく.

.emacs には,以下の設定を書いておけば OK.

(require 'dokuwiki)
(setq dokuwiki-xml-rpc-url "https://www.tsukune.org/skk/doku/lib/exe/xmlrpc.php")
(setq dokuwiki-login-user-name "skk")

1.10.2.1.1 はまった部分

以下のようなメッセージが出て,うまく行かなかった.org2blogでpublish時に固まる(Emacs25.1, xml-rpc 20160430.1458)を確認したら,xml-rpc.el のマルチバイト処理が悪い時代があった模様.

error in process sentinel: url-http-create-request: Multibyte text in HTTP request: POST /????/xmlrpc.php HTTP/1.1 …

ただ,~/.emacs.d/elpa/xml-rpc-20200907.42/ のソースをみても,上記のブログの変更はされていたので,以下の lisp を *scratch* に書いて,行末で C-x C-e と打ち,バージョン番号を確認したら,やけに古いバージョンが表示された.

xml-rpc-version

コードを探してみると,古い wp-emacs を自分でダウンロードしてロードしたのが残っていたので,削除.無事,動作.

1.10.2.2 使いかた

  1. M-x dokuwiki-login
  2. M-x dokuwiki-list-pages
    この時,Tab を押せばいくつかページが出てくるが,表示されないページも多い.ただし,存在しているページならば,正しく名前を入力すれば編集できる.
  3. M-x dokuwiki-save-page
    色々聞かれる中で,summary が何なのか,minor な変更とは何か,はまだ不明.時々,login session が切れたように,ユーザ名・パスワードが聞かれる時があるが,ここで入力しても結局失敗するので,一度 Ctrl-g して,再度 dokuwiki-login をするのが確実.

ちなみに,dokuwiki-list-pages から取得したページは,howm のフォルダの中に保存しておけば,howm 管理ファイルにできる.拡張子は,.dwiki にしておかないと,ページ名との整合性がとれなくなる.

Web 上で編集してしまった場合は,emacs 上の該当 .dwiki ファイルを開いた状態で,M-x dokuwiki-open-page から最新の情報を取得すれば,.dwiki のバッファ内が,最新のデータに更新される.[2023-09-07]

howm でリストアップされるようにするため,このサイトでは,トップに =[memo] のように,イコールで始まるようにしている.

1.10.2.3 バグ [2023-10-15]

前から変だな,と思ってたけど,一度ログインして,しばらく経ってから M-x dokuwiki-save-page しようとした時,ユーザ名を聞かれたら,何かがタイムアウトしてアップデートできなくなる.回避するには,一度 M-x dokuwiki-login しないといけない.

これはすでに issue #5 として報告されてたので,是非改修されると嬉しいが,数年放置されてるんだよなあ…

あ.作者が 2023-02 の時点で,「僕,もう dokuwiki 使ってないんだよね」て言ってる… (リンク).一応,PR に反応はしてるし,何かの活動はしてるんだと思うけど…


1.11 markdown-mode

1.11.1 概要

markdown を書くためのモード.正直 org-mode の方が編集力が高いので好みだが,markdown を書かなければならないことも多いので一応設定しておく.

1.11.2 インストール

M-x package-list-package から markdown-mode を選択してインストール.use-package を使う場合は,自動でインストールされるので,手動でのインストールは必要無い.

1.11.3 設定

(autoload 'markdown-mode' "markdown-mode"
  "Major mode for editing Markdown files" t)
(add-to-list 'auto-mode-alist '("\\.markdown\\'" . markdown-mode))
(add-to-list 'auto-mode-alist '("\\.md\\'" . markdown-mode))
 
(add-hook 'markdown-mode-hook 
	  (lambda ()
	    (turn-off-auto-fill)
	    (action-lock-mode)
	    ))

改行されないように,turn-off-auto-fill を,日付などを入れやすくするために,action-lock-mode を,markdown-mode になる際に起動するようにしている.

[2023-11-16]

use-package を使うなら下記.

(use-package markdown-mode
  :ensure t
  :after action-lock
  :init
  (add-to-list 'auto-mode-alist '("\\.markdown\\'" . markdown-mode))
  (add-to-list 'auto-mode-alist '("\\.md\\'" . markdown-mode))
  :hook
  (markdown-mode . turn-off-auto-fill)
  (markdown-mode . action-lock-mode)
  ;;(define-key markdown-mode-map "\C-cg" 'grep-checkbox-in-md)
  :bind
  (:map markdown-mode-map
        ("C-c g" . grep-checkbox-in-md)
        ("C-c C-y" . skk/markdown-paste-image))
  :custom-face
  (markdown-header-face-1 ((t (:inherit org-level-1))))
  (markdown-header-face-2 ((t (:inherit org-level-2))))
  (markdown-header-face-3 ((t (:inherit org-level-3))))
  (markdown-header-face-4 ((t (:inherit org-level-4))))
  (markdown-header-face-5 ((t (:inherit org-level-5))))
  (markdown-header-face-6 ((t (:inherit org-level-6))))
 
  )

1.11.4 画像を貼りつける

[2023-11-16]

org-mode のところで作った関数を markdown-mode でも使えるようにした.

(defun skk/markdown-paste-image () 
  (interactive)
  (skk/paste-image "markdown")
  )

画像の表示/非表示の toggle は,M-x markdown-toggle-inline-images で,C-c C-x TABまたは C-c C-x C-i

上のセクションに書いてあるように,C-c C-yskk/markdown-paste-image にマップしている.

表示される画像の最大サイズを指定したい場合は,以下のようにする.ただし,imagemagick オプション付きでコンパイルされていないと,縮小は動作しない.Ubuntu の場合はソースからコンパイルしたら良さそう.FreeBSD の場合は,ports でインストールする際のオプションで指定しておくと良さそう.

(setq markdown-max-image-size '(1000 . 700))

1.11.5 pandoc で markdown から PDF 生成 [2023-10-12]

Windows のアプリで,markdown で書いたものを印刷するちょうどよいアプリが無い.アプリを買うのも嫌なので,pandoc を設定した.pandoc 自体の設定は pandoc 参照.

毎度,ターミナルから該当ディレクトリに移動して,長い pandoc コマンドのオプションを入力するのがめんどくさいので,関数にしておいた.しょっちゅう使うならば,global-set-key などで何らかのキーを割り当てると良いかもしれない.markdown-mode だけで起動できるようにしておいてみたが,多分,特定モードでのみ関数を利用できるようにする方法は,mode 名を if で分岐するのではないやりかたがある気がする.

template で指定しているものについては,pandoc参照.

pandoc-mode というものもあり,PDF 製作の方法も書かれている.ちゃんと使いこなせば HTML など他のフォーマットへの出力も全てコントロール出来るものと思われる.ただし,pdf-engine の指定方法など,Usage ページを見てもパッと分からなかったので,pandoc コマンドを直接呼び出す形を今はとっている.時間があったら読み込んでみてもいいかもしれない.

;; pandoc を使って,現在編集している buffer のファイルを pdf に変換する.
(defun markdown-to-pdf-bypandoc ()
  "convert current Markdown buffer to pdf"
  (interactive)
 
  (if (string-match (symbol-name major-mode) "markdown-mode")
      (progn
	(message "current major-mode is markdown-mode. start process.")
	(start-process
	 "pandoc" "mdtopdf-process" 
	 "/usr/bin/pandoc"
	 (buffer-file-name)
	 "--template=/home/skk/svn/howm/pandoc_default.latex"
	 "-o" (replace-regexp-in-string "\\\.md" ".pdf" (buffer-file-name))
	 "--pdf-engine=lualatex"
	 "-V" "documentclass=bxjsarticle"
	 "-V" "classoption=pandoc"
	 "-V" "classoption=12pt"
	 "--number-sections"
	 ))
    (message "currently, major-mode isn't markdown-mode. don't process."))
  )

1.12 markdown-preview-mode

1.12.1 概要

基本的に,wiki/markdown/tex のような記法は,大体書いたものがどのように表示されるか理解しつつ利用するので,プレビューは必要ないが,人に見せながら編集するような場合,少し装飾された形で表示されると都合が良い.

markdown-preview-mode は,emacs 上で 9000 番ポート(デフォルト)で http サーバを立ち上げ,websocket を利用してリアルタイムな情報のアップデートを実現している模様.以下が特徴だ,と書いてある.

  • on save/idle preview update
  • scroll sync
  • custom/extra css and javascript
  • remote preview
  • multiple simultaneous previews

CSS が適用できるのは当然として,スクロール追従をなんとなくしてくれるのは,思ったより便利かもしれない.

1.12.2 設定

use-package の場合.

(use-package markdown-preview-mode
  :ensure t
  :config
  (setq markdown-command (list "/usr/bin/pandoc" "-f" "markdown_github"))
  (setq markdown-preview-stylesheets (list (concat homedir "howm/md/dtext-business/redmine.css"))))

use-package を使わない場合.

(require 'markdown-preview-mode)
(setq markdown-command (list "/usr/bin/pandoc" "-f" "markdown_github"))
(setq markdown-preview-stylesheets (list (concat homedir "howm/md/dtext-business/redmine.css"))))

pandoc 以外にも,markdown や multimarkdown という変換コマンドが存在している模様.pandoc はなんでも変換できるので便利だが,FreeBSD だと ports でインストールするのにでかいし,サイズもでかい.専用で考えるなら,他のコマンドを調べてもいいのかもしれない.

今回は,redmine の markdown を編集したいので,似てる亜種の github markdown をベースにするために,-f markdown_github を指定している.

stylesheet は,redmine の css を持ってきて,以下の変更を加えると,ヘッダを除いた部分が redmine wiki とほぼ同じようになる.

+ ''#content'' を ''#markdown-body'' に変更
+ body に background-color:#EEEEEE; を追加

redmine の CSShttps://redmine host/stylesheets/application.css に置かれているので,wget/fetch してくるとよい.

きれいな CSS の方が良ければ,https://thomasf.github.io/solarized-css/ から CSS を持ってきてもよいかもしれない.

使いかたは,MarkDown を編集しているバッファで M-x markdown-preview-mode と実行すると,ブラウザが開く.http://localhost:9000/?uuid=93fd19c6-0693-4c74-a1d8-62155f7bea4d こんな感じの URL になっているはず.


1.13 outline-magic

1.13.1 基本

dokuwiki-mode と連携して,org-mode のように文章を折り畳んでくれる.

M-x package-list-packages から,探してインストール. 以下の設定で有効化.

(require 'outline-magic)

1.13.2 ショートカット

  • カーソルのある部分のツリーの折り畳みを行う (outline-cycle)
    • Tab
  • ツリー全部の折り畳み/展開を行う
    • C-u Tab
  • M-Shift-<right>/M-Shift-<left> で階層の変更.下位の階層も同時に変更してくれる.

1.14 org-mode

omni-outliner みたいな感じで,テキスト処理を支援してくれるモード.

1.14.1 org-mode で画像をペーストする (WSL環境)

[2023-11-08]

<TBD>

以下二つの blog 記事のコードを混ぜた.

いくつかはまりポイント:

  • たまに落ちてる,wsl 環境のパス変換方法だと,いつも使ってるパスしていと相性が悪いので,今回は両方書いている.
  • Windows のクリップボードにコピーされた画像を取得するのは,以下のように Powershell の機能を利用する.
    • (Get-Clipboard -Format image).Save('somefilename.png')
  • 画像のファイル名にドットが複数あると,LaTeX を利用して PDF を生成する際に,LaTeX で利用している graphicx というライブラリがエラーを吐くので,拡張子以外のドットはアンダーバーに置換する.
  • デフォルトだと画像は生のサイズで表示されるので,org-image-actual-width で横幅を固定にしてしまう.
  • 以下のコードでは,org-mode の画像記述を insert した後,(org-display-inline-images) で画像を表示してしまっているが,少なくとも WSL 上の emacs を X で飛ばしている環境では画像を表示していると動作が激しく重いので,C-c C-x C-v (org-toggle-inline-images) で表示を toggle しながら作業した.(今試したら,そこまで重くなかった.)

画像を保存するディレクトリは,僕は,howm で管理されているディレクトリ以下でしか利用しないだろうから,howm のディレクトリ構成を前提に作っているが,いろいろなところにファイルが点在する人は,WSL 環境と Windows 環境のパス名の変換を正しく行ない,特定のフォルダに画像を保存するようにした方がよいと思われる.

[2023-11-16] markdown でも画像ペーストができるように,少し修正. 画像のインライン表示/非表示の切替えは,M-x org-toggle-inline-images で,C-c C-x C-v

(setq org-image-actual-width 700) ;; emacs 内での画像サイズを実サイズにしない
 
(defun skk/org-paste-image () 
  "wrapper to skk/paste-image"
  (interactive)
  (skk/paste-image "org")
  )
 
(defun skk/paste-image (mode)
  "Save a clipboard's screenshot into a time stamped unique-named file
   in a specified directory and insert a link to this file."
  (interactive)
  ;; XXX ホスト毎に以下は変更する
  (setq filedir "C:\\Users\\skk\\svn\\howm\\img\\")
  (setq unix_filedir (concat homedir "howm/img/"))
 
  ;; latex に入れようとした時,dot が二つあると動作しないので,拡張子用以外の dot は _ にする.
  (setq filename (concat (replace-regexp-in-string "\\." "_" (buffer-name)) "_" (format-time-string "%Y%m%d_%H%M%S_") ".png"))
  (setq filepath
        (concat filedir filename))
 
  (call-process "powershell.exe" nil "*powershell-result*" nil
                "-Command" (concat "(Get-Clipboard -Format image).Save('" filepath "')"))
 
  ;; svn/howm/2023/10/2023-10-19-200323.howm
  ;; ../../img/{filename}
  (if (file-exists-p (concat unix_filedir filename))
      (progn
        (cond
         ((string= mode "org")
          (insert (concat "[[" "../../img/" filename "]]"))
          (org-display-inline-images))
         ((string= mode "markdown")
          (insert (concat "![](../../img/" filename ")"))
          (markdown-display-inline-images))
         ))
    (user-error
     "Error pasting the image, make sure you have an image in the clipboard!"))
  )

1.14.2 PDF 生成

org-mode で書いた文章は,HTML や MarkDown, LaTeX などに Export できる. 多少の文章を作るのに,Word や Google Spreadsheet で作成しても良いのだが,テキストベースで残っている方が保存の観点からは安心なので,共同作業が求められない場合,僕は emacs で文章を書き,LaTeX に export した上で PDF にする機能をちょいちょい使っている.org-mode は MarkDown よりも記述力が高いので,そこそこ複雑な文章でも作成しやすいような気がする.世の中的には MarkDown の方が流行っているけれども.

1.14.2.1 基本設定

org-latex-pdf-process で,呼び出す latex コマンドを指定する.もし,参照を使いたい人は,bibtex などもここで指定する必要がある.また,latexmk などを使って呼び出す内容をまとめておくのもよいかもしれない.僕はコマンドラインから呼び出すことはほぼないので,latexmk まで整備する必要はない.

org-latex-default-class は,org-latex-classes で指定された複数の class のうち,文章内で #+LATEX_CLASS が指定されてない時に利用されるデフォルトを指定しておく.

org-latex-classes には,複数のクラスを記述して追加しておく.以下の例で言えば,“article”の部分をユニークな文字列にしておけば,いくつ登録しておいても良い.

(use-package org
  :ensure t ;; 多分イラナイ
  :after action-lock
  :init
  (setq org-latex-default-class "article")
  (setq org-latex-pdf-process  ; default 
        '("platex -shell-escape %f"
          "platex -shell-escape %f"
          "dvipdfmx %b.dvi"))
  (setq org-export-with-toc nil) ; \tableofcontents を出さない
  (setq org-export-latex-date-format "\\today") ; \date{} の中身を \today にする
  :hook
  (org-mode . turn-off-auto-fill)
  (org-mode . action-lock-mode)
  :config
  (add-hook 'org-mode-hook (lambda () (setq truncate-lines nil)))
  (add-hook 'org-export-before-processing-functions 'skk/latex-engine-setup)
  )
 
(add-to-list 'org-latex-classes
             '("article"
               "\\documentclass[11pt,a4paper]{jarticle}
\\setlength{\\topmargin}{15mm}
\\addtolength{\\topmargin}{-1in}
\\setlength{\\oddsidemargin}{20mm}
\\addtolength{\\oddsidemargin}{-1in}
\\setlength{\\evensidemargin}{15mm}
\\addtolength{\\evensidemargin}{-1in}
\\setlength{\\textwidth}{170mm}
\\setlength{\\textheight}{254mm}
\\setlength{\\headsep}{0mm}
\\setlength{\\headheight}{0mm}
\\setlength{\\topskip}{0mm}
\\usepackage{otf}
\\usepackage{fancyhdr}
\\usepackage[dvipdfmx]{graphicx,color}
\\usepackage{xytree}
\\usepackage{indentfirst}
\\usepackage{minijs}
\\usepackage[T1]{fontenc}
\\usepackage{lmodern}
\\pagestyle{plain}
\\makeatletter
\\西暦"
               ("\\section{%s}" . "||section*{%s}")
               ("\\subsection{%s}" . "\\subsection*{%s}")
               ("\\subsubsection{%s}" . "\\subsubsection*{%s}")
               ("\\paragraph{%s}" . "\\paragraph*{%s}")
               ("\\subparagraph{%s}" . "\\subparagraph*{%s}")
               )
 
)

org の文章をエクスポートするには,C-c C-e と打ち,エクスポートのメニューを出す.LaTeX export は,l で選択できて,p とすると,PDF まで生成される.

Windows の emacs で作業する際,PDF をオープンするために,毎回,howm で保存されてるフォルダにまでエクスプローラでたどり着くのがめんどくさいので,C-x 3 と打つことで編集しているフォルダを開けるように,以下のような設定も追加している.

(global-set-key "\C-c3" 'open-current-file-dir-with-explore)
(defun open-current-file-dir-with-explore ()
  (interactive)
  (my-action-lock-explorer-open (unix-path-to-windows (file-name-directory buffer-file-truename)))
  )

実際に文章を書く際は,文章毎にいくつかオプションをつけることになる.次の項目に僕が使う代表的なものをいくつか載せておくが,クラスを選ぶには,#+LATEX_CLASS: article のように指定すると良い.

1.14.2.2 文章毎のオプション

#+TITLE: 文章のタイトル
#+AUTHOR: skk 
#+LATEX_CLASS: plainarticle
* セクション
内容内容.

のように書く.オプションは以下のようなものがある.

コマンド 内容
#+TITLE: タイトル文字 LaTeX の \title.文章のタイトルを指定する
#+AUTHOR: 著者 著者を指定
#+DATE: \today 日付を指定.\today としておくと今日の日付になる
#+OPTIONS: H:2 toc:nil num:t
#+LANGUAGE: ja
#+LATEX_CLASS: plainarticle org-latex-class で命名した名前を指定し,適用される LaTeX ヘッダなどを選ぶ.

<TBD>

1.14.2.3 latex engine の切替え

僕は 2000 年代頭くらいの状態のまま LaTeX を使っていたので,platex を使っていたのだが,ここ最近は,lualatex,uplatex, pdflatex などが出てきていたり,latexmk のようなエンジンを切替えるしくみが出てきていたりする. これと同じように,documentclass で指定するクラスも,jarticle ではなく,jsarticle や bxjsarticle などを使うのが主流になってきている模様.

年賀状で原の味フォントを使うのなんかも,platex のままで動作してたので,問題ないのかもしれないが,せっかくちょっとずついじるなら, jsarticle などを使っていってみたいと思う.

ただ,その際,jarticle ベースで設定していたフォーマットと,jsarticle でこれから育てていくフォーマットを共存させたくなってくるので,latex class に応じて呼び出されるコマンドを変更したいと考えた.

org-mode で日本語LaTeXを出力する方法がかなり僕のやりたいことに近い気がするのだが,微妙に理解できないのと,使いかたも微妙に分からない.てか,マッチョすぎる.自分が latex-class で指定した名前に応じてエンジンが選べれば良い. Getting keyword options in org-filesを見てみると,org における keyword を取得するためのコードが書いてあった.#+ で始まる行は,org のキーワードというものらしいので,これが取得できれば良さそう. ここのコードを拝借して,以下のように書いてみたら,切替がうまく言ったような気がする.

(use-package org
  ... 
  :hook 
  (add-hook 'org-export-before-processing-functions 'skk/latex-engine-setup)
)
 
(defun skk/latex-engine-setup (engine)
  (message "engine: %s" engine)
  ;; [2025-01-12]
  ;; https://kitchingroup.cheme.cmu.edu/blog/2013/05/05/Getting-keyword-options-in-org-files/
  (setq current-latex-class
        (cdr (assoc "LATEX_CLASS"
                    (org-element-map (org-element-parse-buffer 'element) 'keyword
                      (lambda (keyword) (cons (org-element-property :key keyword)
                                              (org-element-property :value keyword)))))))
  (if (eq current-latex-class nil)
      (setq current-latex-class org-latex-default-class))
 
  (cond ((string-match current-latex-class "article")
         (setq org-latex-pdf-process  ; default 
               '("platex -shell-escape %f"
                 "platex -shell-escape %f"
                 "dvipdfmx %b.dvi"))
         (delete "\\hypersetup{setpagesize=false}" org-latex-packages-alist)
         (delete "\\hypersetup{colorlinks=true}" org-latex-packages-alist)
         (delete "\\hypersetup{linkcolor=blue}" org-latex-packages-alist))
        ((string-match current-latex-class "beamer")
         (setq org-latex-pdf-process  ; default 
               '("platex -shell-escape %f"
                 "platex -shell-escape %f"
                 "dvipdfmx %b.dvi"))
         (delete "\\hypersetup{setpagesize=false}" org-latex-packages-alist)
         (delete "\\hypersetup{colorlinks=true}" org-latex-packages-alist)
         (delete "\\hypersetup{linkcolor=blue}" org-latex-packages-alist))
        ((string-match current-latex-class "jsarticle")
         (setq org-latex-pdf-process  ; default 
               '("uplatex -shell-escape %f"
                 "uplatex -shell-escape %f"
                 "dvipdfmx %b.dvi"))
         (add-to-list 'org-latex-packages-alist "\\hypersetup{setpagesize=false}" t)
         (add-to-list 'org-latex-packages-alist "\\hypersetup{colorlinks=true}" t)
         (add-to-list 'org-latex-packages-alist "\\hypersetup{linkcolor=blue}" t))
        )
  )

org-export-before-processing-functions が,export がはじまる前に呼び出される hook である.以前は,org-export-before-processing-hook という名前だったようなので,検索してるとこちらがよく引っかかるが,M-x describe-functions で調べてみると,obsolute だよ,と書いてあるので,これから使う人は気をつけた方が良いかもしれない.

skk/latex-engine-setup では,文章内に LATEX_CLASS が指定してあればそれを利用するし,見つからなかったら default の class を利用する.文字列で分岐して,org-latex-pdf-processの中身を書き換える,ということをしている.org-latex-package-alistを操作しているのは,\CID をタイトルで利用しているとうまく動作しないことの実験なので,別のところで書く予定.

備考:Emacs org-mode latex - simply switch between pdflatex, xelatex and lualatex を見ると,#+LATEX_COMPILER を指定し,latexmk に渡してさらっと変更してるみたい.これはこれで自由度高いし便利そうだけど,LATEX_CLASS で使いたいクラスを指定しつつ,コンパイラも指定しないといけなくなるから,うーん,どうだろう...好みかもしれないけど,僕は,今の自分の設定の方が良い気がするな.

1.14.2.4 beamer

LaTeX の beamer クラスは,スライドを LaTeX で作成するためのクラス.org で箇条書したものを beamer で export すると,スライドにしやすい.ox-beamer というパッケージを使いつつ export できる.

beamer の LaTeX 的な記法は,latex に記載予定.

<TBD>

1.14.3 tbl 関係

テーブルを管理できる.tab で行を追加したり,表計算したり,csv,html との変換も行なえる.

1.14.4 csv のインポート

M-x org-table-import

で,指示通りにファイルを指定したら,インポートされる.

1.14.5 csv のエクスポート

カーソルをテーブル上に置いた状態で,

M-x org-table-export

とし,形式を指定する.

orgtbl-to-tsvタブ区切りのテキスト形式
orgtbl-to-csvカンマ区切りのテキスト形式
orgtbl-to-htmlHTML形式
orgtbl-to-latexLaTeX形式

1.15 lisp-mode

1.15.1 .el 以外を lisp と認識させる

  • ファイルの先頭に以下を書いておくと,file open 時に自動的に lisp-mode に入る
    • ; -*- Emacs-Lisp -*-

1.16 c-mode

ショートカット:

形式コメント挿入M-;
指定バッファコメントアウトバッファを選択してから C-c C-c
指定バッファアンコメントバッファを選択してから C-u C-c C-c
自動改行のtoggleC-c C-a
タグ・ジャンプM-.
ジャンプ前の位置に戻るM-*
関数名の補完入力M-TAB(初回 M-x visit-tags-table により TAGS を読み込ませる)
標準関数の補完入力(libcの関数)C-u M-TAB

1.17 pukiwiki mode

  • M-x pukiwiki-edit の後に,ページの名前を入力する.
  • コマンド
    • インデックス時に使えるコマンド
      Rインデックス更新
    • 編集してるときに使えるコマンド
      C-c C-pプレビュー
      C-c C-cページ更新

1.17.1 参考ページ

1.17.2 emacs-23 対応

emacs 23 だと,文字コード関係で下記 2 つの問題が起きるので,ソースを直 接書き換えることで対応.

1) pukiwiki-mode.el 3200 行目付近 (日本語タイトルのページが処理できない問題)

 (decode-coding-string result coding)

 (decode-coding-string (eval (cons 'unibyte-string
                                 (string-to-list result)))
                              coding)

2) 3025 行目付近

 (if (featurep 'xemacs)
             (find-charset-region (point-min) (point-max))
             (coding-system-get mycodingsystem 'safe-charsets))
 

 (find-charset-region (point-min) (point-max))
  • 参考:

http://i-yt.info/?date=20091121


1.18 svn mode

1.18.1 new (2010/05)

  • psvn の方が使いやすそう.
  • freebsd なら ports から psvn をインストールしておく.
  • .emacs
    • (require 'psvn)
  • 使い方
    • M-x svn-status
      こうすると,dird のような画面.
  • コマンド
    • c: commit
    • U: update
  • コメントモードになったら,以下で実際のコミットとなる.
    • C-c C-c

1.18.1.1 参考

1.18.2 old

  • コマンド
    C-c v vコミットのためのログ入力画面
    C-c C-c(ログを入力したら)実際にコミット

1.19 table-mode

ascii table が簡単に作れる

以下のコマンド・関数は,table.el 内に書かれている.

コマンドリスト

+------------------------------------------------------------------+
|                Default Bindings in a Table Cell                  |
+-------+----------------------------------------------------------+
|  Key  |                      Function                            |
+-------+----------------------------------------------------------+
|  TAB  |Move point forward to the beginning of the next cell.     |
+-------+----------------------------------------------------------+
| "C->" |Widen the current cell.                                   |
+-------+----------------------------------------------------------+
| "C-<" |Narrow the current cell.                                  |
+-------+----------------------------------------------------------+
| "C-}" |Heighten the current cell.                                |
+-------+----------------------------------------------------------+
| "C-{" |Shorten the current cell.                                 |
+-------+----------------------------------------------------------+
| "C--" |Split current cell vertically. (one above and one below)  |
+-------+----------------------------------------------------------+
| "C-|" |Split current cell horizontally. (one left and one right) |
+-------+----------------------------------------------------------+
| "C-*" |Span current cell into adjacent one.                      |
+-------+----------------------------------------------------------+
| "C-+" |Insert row(s)/column(s).                                  |
+-------+----------------------------------------------------------+
| "C-!" |Toggle between normal mode and fixed width mode.          |
+-------+----------------------------------------------------------+
| "C-#" |Report cell and table dimension.                          |
+-------+----------------------------------------------------------+
| "C-^" |Generate the source in a language from the current table. |
+-------+----------------------------------------------------------+
| "C-:" |Justify the contents of cell(s).                          |
+-------+----------------------------------------------------------+

関数リスト

+------------------------------------------------------------------+
|                    User Visible Entry Points                     |
+-------------------------------+----------------------------------+
|           Function            |           Description            |
+-------------------------------+----------------------------------+
|`table-insert'                 |Insert a table consisting of grid |
|                               |of cells by specifying the number |
|                               |of COLUMNS, number of ROWS, cell  |
|                               |WIDTH and cell HEIGHT.            |
+-------------------------------+----------------------------------+
|`table-insert-row'             |Insert row(s) of cells before the |
|                               |current row that matches the      |
|                               |current row structure.            |
+-------------------------------+----------------------------------+
|`table-insert-column'          |Insert column(s) of cells before  |
|                               |the current column that matches   |
|                               |the current column structure.     |
+-------------------------------+----------------------------------+
|`table-delete-row'             |Delete row(s) of cells.  The row  |
|                               |must consist from cells of the    |
|                               |same height.                      |
+-------------------------------+----------------------------------+
|`table-delete-column'          |Delete column(s) of cells.  The   |
|                               |column must consist from cells of |
|                               |the same width.                   |
+-------------------------------+----------------------------------+
|`table-recognize'              |Recognize all tables in the       |
|`table-unrecognize'            |current buffer and                |
|                               |activate/inactivate them.         |
+-------------------------------+----------------------------------+
|`table-recognize-region'       |Recognize all the cells in a      |
|`table-unrecognize-region'     |region and activate/inactivate    |
|                               |them.                             |
+-------------------------------+----------------------------------+
|`table-recognize-table'        |Recognize all the cells in a      |
|`table-unrecognize-table'      |single table and                  |
|                               |activate/inactivate them.         |
+-------------------------------+----------------------------------+
|`table-recognize-cell'         |Recognize a cell.  Find a cell    |
|`table-unrecognize-cell'       |which contains the current point  |
|                               |and activate/inactivate that cell.|
+-------------------------------+----------------------------------+
|`table-forward-cell'           |Move point to the next Nth cell in|
|                               |a table.                          |
+-------------------------------+----------------------------------+
|`table-backward-cell'          |Move point to the previous Nth    |
|                               |cell in a table.                  |
+-------------------------------+----------------------------------+
|`table-span-cell'              |Span the current cell toward the  |
|                               |specified direction and merge it  |
|                               |with the adjacent cell.  The      |
|                               |direction is right, left, above or|
|                               |below.                            |
+-------------------------------+----------------------------------+
|`table-split-cell-vertically'  |Split the current cell vertically |
|                               |and create a cell above and a cell|
|                               |below the point location.         |
+-------------------------------+----------------------------------+
|`table-split-cell-horizontally'|Split the current cell            |
|                               |horizontally and create a cell on |
|                               |the left and a cell on the right  |
|                               |of the point location.            |
+-------------------------------+----------------------------------+
|`table-split-cell'             |Split the current cell vertically |
|                               |or horizontally.  This is a       |
|                               |wrapper command to the other two  |
|                               |orientation specific commands.    |
+-------------------------------+----------------------------------+
|`table-heighten-cell'          |Heighten the current cell.        |
+-------------------------------+----------------------------------+
|`table-shorten-cell'           |Shorten the current cell.         |
+-------------------------------+----------------------------------+
|`table-widen-cell'             |Widen the current cell.           |
+-------------------------------+----------------------------------+
|`table-narrow-cell'            |Narrow the current cell.          |
+-------------------------------+----------------------------------+
|`table-fixed-width-mode'       |Toggle fixed width mode.  In the  |
|                               |fixed width mode, typing inside a |
|                               |cell never changes the cell width,|
|                               |while in the normal mode the cell |
|                               |width expands automatically in    |
|                               |order to prevent a word being     |
|                               |folded into multiple lines.  Fixed|
|                               |width mode reverses video or      |
|                               |underline the cell contents for   |
|                               |its indication.                   |
+-------------------------------+----------------------------------+
|`table-query-dimension'        |Compute and report the current    |
|                               |cell dimension, current table     |
|                               |dimension and the number of       |
|                               |columns and rows in the table.    |
+-------------------------------+----------------------------------+
|`table-generate-source'        |Generate the source of the current|
|                               |table in the specified language   |
|                               |and insert it into a specified    |
|                               |buffer.                           |
|`table-insert-sequence'        |Travel cells forward while        |
|                               |inserting a specified sequence    |
|                               |string into each cell.            |
+-------------------------------+----------------------------------+
|`table-capture'                |Convert plain text into a table by|
|                               |capturing the text in the region. |
+-------------------------------+----------------------------------+
|`table-release'                |Convert a table into plain text by|
|                               |removing the frame from a table.  |
+-------------------------------+----------------------------------+
|`table-justify'                |Justify the contents of cell(s).  |
+-------------------------------+----------------------------------+

1.20 outline-mode

1.20.1 概要

文章を構造化して見せるためのもの.

これから,構造化した文章を emacs で書きたい場合は,org-mode の利用が良いと思われる.[2023-09-08]

1.20.2 モードへの入り方

M-x outline-mode 

1.20.3 書き方

* 1 章
  内容
* 2 章
  内容
** 2.1 節
  内容

1.20.4 コマンド一覧

  • カーソルのある行の中身部分を隠す.(hide-subtree)
    • C-c C-d 
  • 隠されているのを全部開く.(show-all)
    • C-c C-a
  • 全部閉じる (hide-other)
    • C-c C-t 
  • 一つの段落を閉じる (hide-body)
    • C-c C-c
  • 隠されている一つの段落を開く (show-body)
    • C-c C-e 

1.20.5 参考ページ

2. 日本語入力

2.1 tamago/egg

emacs から FreeWnn を使う為に利用する FEP.1990 年代の中盤くらいでは,egg v3 というものがメジャーだった気がするが,2000 年代に入って少ししてからは,egg v4 がメジャーになった記憶.egg v3 はまだ FreeWnn の C ライブラリを利用して FreeWnn に接続していたが,egg v4 からは,FreeWnn への接続も含めて全て elisp で書き直された,という違いだった気がする.

v3 はある程度色々な機能がつまっていて,例えば,ユーザ辞書の編集機能や,編集時の face 変更なども可能だったが,v4 は elisp で変換できる最低限の部分が作られた後,egg の開発が下火になり,細かい部分の実装まで手が回っていないように見受けられる.

当然のことながら [2023-09-08] 時点でほぼメンテナンスされてないので,ちょこちょこ手を加えないと使いにくい.

2.1.1 疑問点

変換候補一覧は,M-z だとずっと思っていたが,どうも M-s でも何か出てくる.

M-s は,egg-select-candidate-major が呼ばれる模様.

2.1.2 tamago/egg で接続する jserver を指定する (2013/10/10)

(setq wnn-jserver "hostname.domain")
  • FreeWnn をホスト外からTCP接続できるようにするのは、FreeBSD のページを参照。

2.1.3 tamago の emacs 23 対応

2011/12 月時点でも,freebsd の ports で入る tamago はいくつか不便なと ころがあるので,現状はソースを直接変更するしかなかったり.

2.1.3.1 tamago で入力途中に C-h で文字を削除する方法

  • tamago を直接書き換えるしか分からない.
  • /usr/local/share/emacs/23.2/site-lisp/egg/its.el の 267 行目近辺.
    •   ;  (define-key map "\C-h" 'its-mode-help-command)
           (define-key map "\C-h" 'its-delete-backward-SYL)

2.1.3.2 lisp の書き換え1

http://www.m17n.org/mlarchive/mule-ja/200703/msg00018.html

  In article <E1HUjBZ-0000IB-Kx@xxxxxxxxxxxxxxx>, Kenichi Handa <handa@xxxxxxxx> writes:
  
  > > Emacs 23.0.0 では,漢字変換が出来ず,mini buffer に 「1つも候補を作れませ
  > > んでした」というエラーが表示されます.
  
  > tamago はコード変換 (coding-system fixed-euc-jp) の部分で
  > emacs の内部文字コードに依存した部分があるため、上手くいかな
  > いのだと思います。今ちょっと忙しいので来週にでも見てみます。
  
  tamago に以下のパッチをあてたものを Emacs 23 で試してもらえま
  せんか。
  
  ---
  半田@AIST
  
  diff -c /project/mule/tamago/egg-com.el /project/mule/tmp/tamago/egg-com.el
  *** /project/mule/tamago/egg-com.el	Sun Jan 28 03:53:13 2001
  --- /project/mule/tmp/tamago/egg-com.el	Mon Mar 26 10:10:26 2007
  ***************
  *** 44,105 ****
    
    ;; Japanese
    
  ! (eval-and-compile
  ! (define-ccl-program ccl-decode-fixed-euc-jp
  !   `(2
  !     ((r2 = ,(charset-id 'japanese-jisx0208))
  !      (r3 = ,(charset-id 'japanese-jisx0212))
  !      (r4 = ,(charset-id 'katakana-jisx0201))
  !      (read r0)
  !      (loop
  !       (read r1)
  !       (if (r0 < ?\x80)
  ! 	  ((r0 = r1)
  ! 	   (if (r1 < ?\x80)
  ! 	       (write-read-repeat r0))
  ! 	   (write r4)
  ! 	   (write-read-repeat r0))
  ! 	((if (r1 > ?\x80)
  ! 	     ((write r2 r0)
  ! 	      (r0 = r1)
  ! 	      (write-read-repeat r0))
  ! 	   ((write r3 r0)
  ! 	    (r0 = (r1 | ?\x80))
  ! 	    (write-read-repeat r0)))))))))
  ! 
  ! (define-ccl-program ccl-encode-fixed-euc-jp
  !   `(2
  !     ((read r0)
  !      (loop
  !       (if (r0 == ,(charset-id 'latin-jisx0201))                   ; Unify
  ! 	  ((read r0)
  ! 	   (r0 &= ?\x7f)))
  !       (if (r0 < ?\x80)                                            ;G0
  ! 	  ((write 0)
  ! 	   (write-read-repeat r0)))
  !       (r6 = (r0 == ,(charset-id 'japanese-jisx0208)))
  !       (r6 |= (r0 == ,(charset-id 'japanese-jisx0208-1978)))
  !       (if r6                                                      ;G1
  ! 	  ((read r0)
  ! 	   (write r0)
  ! 	   (read r0)
  ! 	   (write-read-repeat r0)))
  !       (if (r0 == ,(charset-id 'katakana-jisx0201))                ;G2
  ! 	  ((read r0)
  ! 	   (write 0)
  ! 	   (write-read-repeat r0)))
  !       (if (r0 == ,(charset-id 'japanese-jisx0212))                ;G3
  ! 	  ((read r0)
  ! 	   (write r0)
  ! 	   (read r0)
  ! 	   (r0 &= ?\x7f)
  ! 	   (write-read-repeat r0)))
  !       (read r0)
  !       (repeat)))))
  ! )
  ! 
  ! (make-coding-system 'fixed-euc-jp 4 ?W "Coding System for fixed EUC Japanese"
  ! 		    (cons ccl-decode-fixed-euc-jp ccl-encode-fixed-euc-jp))
    
    ;; Korean
    
  --- 44,110 ----
    
    ;; Japanese
    
  ! (defun fixed-euc-jp-pre-write-conversion (from to)
  !   (let ((work-buf (generate-new-buffer " *temp*"))
  ! 	ch)
  !     (if (stringp from)
  ! 	(encode-coding-string from 'euc-japan nil work-buf)
  !       (encode-coding-region from to 'euc-japan work-buf))
  !     (set-buffer work-buf)
  !     (set-buffer-multibyte nil)
  !     (goto-char (point-min))
  !     (while (not (eobp))
  !       (setq ch (following-char))
  !       (cond ((= ch #x8E)		; SS2 for JISX0201-kana
  ! 	     (delete-char 1)		; SS2 BYTE -> 0 BYTE&0x7F
  ! 	     (insert 0)
  ! 	     (forward-char 1))
  ! 	    ((= ch #x8F)		; SS3 for JISX0212
  ! 	     (delete-char 1)		; SS3 BYTE1 BYTE2 -> BYTE1 BYTE2&0x7F
  ! 	     (forward-char 1)
  ! 	     (setq ch (following-char))
  ! 	     (delete-char 1)
  ! 	     (insert (logand ch #x7F)))
  ! 	    ((>= ch #xA0)		; JISX0208
  ! 	     (forward-char 2))
  ! 	    (t				; ASCII
  ! 	     (insert 0)			; BYTE -> 0 BYTE
  ! 	     (forward-char 1))))))
  ! 
  ! (defun fixed-euc-jp-post-read-conversion (len)
  !   (let ((str (string-as-unibyte (buffer-substring (point) (+ (point) len))))
  ! 	(pos (point))
  ! 	i ch1 ch2)
  !     (delete-region (point) (+ (point) len))
  !     (setq i 0)
  !     (while (< i len)
  !       (setq ch1 (aref str i))
  !       (setq ch2 (aref str (1+ i)))
  !       (cond ((>= ch1 #x80)
  ! 	     (if (>= ch2 #x80)
  ! 		 (setq ch1 		; JISX0208
  ! 		       (decode-char 'japanese-jisx0208
  ! 				    (logior (lsh (logand ch1 #x7F) 8)
  ! 					    (logand ch2 #x7F))))
  ! 	       (setq ch1		; JISX0212
  ! 		     (decode-char 'japanese-jisx0212
  ! 				  (logior (lsh (logand ch1 #x7F) 8) ch2)))))
  ! 	    (t
  ! 	     (if (>= ch2 #x80)
  ! 		 (setq ch1		; JISX0201-kana
  ! 		       (decode-char 'katakana-jisx0201 (logand ch2 #x7F)))
  ! 	       (setq ch1 ch2))))
  !       (insert ch1)
  !       (setq i (+ i 2)))
  !     (prog1 (- (point) pos)
  !       (goto-char pos))))
  ! 
  ! (define-coding-system 'fixed-euc-jp "Coding System for fixed EUC Japanese"
  !   :mnemonic ?W
  !   :coding-type 'raw-text
  !   :charset-list '(ascii japanese-jisx0208 katakana-jisx0201 japanese-jisx0212)
  !   :pre-write-conversion 'fixed-euc-jp-pre-write-conversion
  !   :post-read-conversion 'fixed-euc-jp-post-read-conversion)
    
    ;; Korean

2.1.3.3 elisp の書き換え2(org-mode の書き換え)

http://d.hatena.ne.jp/grandVin/20090129/1233226833

tamagoは、入力された日本語文字を解析する際に、プロパティの属性 intangible に 'its-part-2が設定されているのを期待している。

しかし orgモードでは、ナナ何と、tamago が設定した intangible属性を上書きしてしまうのだ!ナンテヒドイ

で、どう対処したかというと…………、orgソースの全ての intangible を org-intangible に書き換えた。。

行っちゃイカン場所にカーソルが突入しそうな予感もするが、今のところ問題は起きていない。

あまりエレガントな方法では無いなぁ。何かもっといい方法は無いんだろうか。。。

駄菓子菓子、これで念願の org-remember-templates が使えるゾ!!

2.1.4 tamago allbsd 版 での句読点切替え

2.1.4.1 tamago allbsd 版

いつかのタイミングから,FreeBSD ports でダウンロードされる tamago が https://github.com/hrs-allbsd/tamago のものになっている.これだと少なくとも emacs28 や emacs29 などでも問題なく FreeWnn での変換が可能になっている.

もし Linux でインストールする場合は,configure を利用すると良い気がする.僕は少なくとも,WSL emacs での tamago は allbsd の tamago を利用している.

2.1.4.2 句読点切替え [2023-09-13]

多くのサイトに今でも,tamago で句読点を.や,にしたい場合,以下の設定を行なえば良いと書かれている.

(setq its-hira-comma ",")
(setq its-hira-period ".")

だが,ソースコード内の ChangeLog.1997-1998 の 1998-02-17 のエントリに,

Bug fix. Don't set its-hira-period and its-hira-comma.

と書かれており,実際 allbsd な tamago ではこの変数をセットしても動作しない.

代わりに以下のように its-hira-map に対して its-defrule という形で追記すると良いようである.どこかのサイトを参考にしたはずだが,見付けられなくなってしまった.

(eval-after-load "its"
  '(define-its-state-machine-append its-hira-map
     (its-defrule ","  ","      nil t)
     (its-defrule "."  "."    nil t)
     ))

時々。や、を使いたくなる時があるので,以下のように切替が出来るようにしてみた.

;; 句読点を切替える為の関数 [2023-09-13]
(defun toggle-kuten ()
  "句読点を切替える"
  (interactive)
  (cond ((string-match kuten-mode "maru")
	 (eval-after-load "its"
	   '(define-its-state-machine-append its-hira-map
	      (its-defrule ","  ","      nil t)
	      (its-defrule "."  "."    nil t)
	      ))
	 (setq kuten-mode "ten"))
	((string-match kuten-mode "ten")
	 (eval-after-load "its"
	   '(define-its-state-machine-append its-hira-map
	      (its-defrule ","  "、"      nil t)
	      (its-defrule "."  "。"    nil t)
	      ))
	 (setq kuten-mode "maru"))))
 
(setq kuten-mode "maru") ;; ten
(toggle-kuten)

2.1.5 tamago allbsd 版 での変換時の下線 [2023-10-20]

eggv3 の時代は,確か,変換中の領域を示す記号が,|わたしのなまえはなかのです| のようなパイプではなく,下線だった気がする.そして,下線の方がどこを変換しているのか分かりやすくて個人的には好きである.だが,tamago v4 な allbsd 版は,そのオプションがない.face の指定が出来るみたいだが,どうも language 関係の選択しか無いように見える.

elisp がほんのちょっと読めるようになってきたので,一生懸命読んでみて,以下の部分に下線を入れるコードを追加したら,下線が出てくるようになった.何らかの副作用はきっとあるに違いない.すでに,(多分 font-lock によって)色のついている行の中で変換すると,その行の色が消えてしまう問題が見つかっている.ちなみに,コードは allbsd 版だが,動作は WSL Linux の emacs 上で確認している.

put-text-property の式だけコピーすれば良いはず.

L652 くらいから:

egg-cnv.el
    (if face
	(egg-set-face 0 len1 face converted))
 
    ;;skk
    (put-text-property 0 (length converted)
		       'font-lock-face '(:underline t)
		       converted)
 
    converted))

L584 くらいから:

its.el
    (if face
	(egg-set-face 0 (length output) face output))
    ;; by skk 
    (put-text-property 0 (length output)
		       'font-lock-face '(:underline t)
		       output)
    (insert output)

ただし,これだと大本のソースコードを修正しなければならず,アドホック対応にもほどがある.

  • egg-conversion-face で指定して,ちゃんと値を取り回す
  • def-advice/advice-add などで上手に該当の関数を上書きする

などの対応で,大本のソースコードを汚さないか,頑張れたら MR を投げてみても良いのかもしれない.

ちなみに,face を変更する命令は探せば探すほど大量に出てきたので,add-text-property など適当に試した中で動いた命令を使っている.本来,face の理解,各関数の理解をもう少し深めた上で,利用する関数を決めた方が良い気がする.

2.1.6 辞書情報の FreeWnn 側との同期 [2023-10-20]

色々文献を漁っていると,どうも変換した結果の頻度情報は,必ず ud や頻度情報ファイルに反映されてる訳ではない模様.一つめの理由としては,頻度情報を扱うアルゴリズムが,単に頻度情報をインクリメントしてるだけではなく,場合によって増えない,と買いてあったから.次に,egg 側でキャッシュしているように見受けられる.サーバの負荷を軽減するためなのかなんなのかは良く分からない.

下線を引くためにソースコードを眺めていたら,(egg-finalize-backend) という関数を発見した.これを実行してみると,以下のようなメッセージが出てくる.また,その後,継続して日本語入力すると,jserver に接続しました,というメッセージが *Message* バッファに出てきた.(自作の)サーバ側と辞書情報を同期する際は,この関数も呼んでおくと良いのかもしれない.

Wnn の頻度情報・辞書情報を退避しています
Wnn の頻度情報・辞書情報を退避しました

2.2 quail

emacs21 くらいからデフォルトで入ってる日本語入力.開発マシン上でemacsを使う時に便利.

2.2.1 設定

(setq default-input-method "japanese")
(define-key quail-conversion-keymap "\C-h" 'quail-conversion-backward-delete-char) ; 変換中に C-h で前を消す.

2.2.2 使い方

M-x quail-help使い方ヘルプ
qq半角・全角切り替え
qz英語全角モード
qh半角モードへ戻る

2.3 wnn7 関連

2.3.1 インストール

FreeBSD 6 以上にインストール場合,Install スクリプト編集の必要あり. check_FreeBSD() という関数の中で,次のようにする.

 6.* )
     DIST="FreeBSD5"
     # echo "FreeBSD6.x"

FreeBSD 5 に見せかける.このあと,/usr/ports/mics/compat5x もインストー ルする.

2.3.2 wnn7egg キーバインド

2.3.2.1 フェンスモード

[Ctrl]+[w]または[Space] 変換キー
[Ctrl]+[a] カーソルを先頭へ
[Crtl]+[b] カーソルを左へ移動
[Ctrl]+[c] または[Ctrl]+[g] フェンスモードから抜ける
[Ctrl]+[d] カーソル位置の一文字を消去
[Ctrl]+[e] カーソルを末尾に移動
[Ctrl]+[f] カーソルを右に移動
[Ctrl]+[k] カーソルから後ろを削除
[Ctrl]+[l]または[Ret] 文字を確定し、フェンスモードから抜ける
[Ctrl]+[_] JISコード入力
[Ctrl]+[\] ローマ字入力モードと半角入力モードの切り替え
[Ctrl]+[h] ひらがなに変換
[Ctrl]+[k] かたかなに変換

2.3.2.2 漢字変換モード

[Ctrl]+[w] または[Space] 次候補表示
[Ctrl]+[a] 先頭へ移動
[Ctrl]+[b] 次の文節へ移動
[Ctrl]+[c] または [Ctrl]+[g] フェンスモードへ戻る
[Ctrl]+[e] 末尾へ移動
[Ctrl]+[f] 後ろの文節へ移動
[Ctrl]+[i] 文節の長さを短くする
[Ctrl]+[k] カーソルのある文節より前を確定し、その後をかなに戻す
[Ctrl]+[l]または[Ctrl]+[m]または[Ret] 全ての変換を確定する
[Ctrl]+[n] 次候補表示
[Ctrl]+[o] 文節の長さを長くする
[Ctrl]+[p] 前候補表示
[Ctrl]+[v] インスペクト(品詞名や辞書情報を見る)
[Ctrl]+[s] 変換候補一覧表示モード
[Ctrl]+[h] ひらがなに変換
[Ctrl]+[k] かたかなに変換
[Meta]+[r] 連想検索

3. インタラクティブな使い方

3.1 カーソル位置のフォント情報を取得する [2023-10-12]

C-u C-x =

以下のような出力が得られる.

             position: 29809 of 49710 (60%), column: 0
            character: = (displayed as =) (codepoint 61, #o75, #x3d)
              charset: ascii (ASCII (ISO646 IRV))
code point in charset: 0x3D
               script: latin
               syntax: _ 	which means: symbol
             category: .:Base, a:ASCII, l:Latin, r:Roman
             to input: type "C-x 8 RET 3d" or "C-x 8 RET EQUALS SIGN"
          buffer code: #x3D
            file code: #x3D (encoded by coding system utf-8-unix)
              display: by this font (glyph code):
    ftcrhb:-PfEd-HackGen35 Console NF-normal-normal-normal-*-20-*-*-*-*-0-iso10646-1 (#x20)

Character code properties: customize what to show
  name: EQUALS SIGN
  general-category: Sm (Symbol, Math)
  decomposition: (61) ('=')

There are text properties here:
  face                 dokuwiki-headline-3
  fontified            t

3.2 指定した単語で整列させる [2022-09-01]

M-x align-regex “指定文字”

MODE_LIST       = 1
MODE_VIEWER     = 2
MINORMODE_TITLE = 1
MINORMODE_WA    = 2

のようになる.

python-mode のように,何らかのモードを利用している場合は,

M-x align

のみでも,適宜アラインしてくれる.

3.3 フォントサイズを変更する [2021-07-07]

Window モード(ターミナルではない)で利用する機能.

  C-x C-0

の後に,+ か - で変更していく.

3.4 window サイズで,行を折り返す [2019-02-26]

M-x toggle-truncate-lines 

3.5 関数の役割を調べる [2021-03-03]

 M-x describe-function

3.6 window サイズで,行を折り返す [2019-02-26]

 M-x toggle-truncate-lines 

3.7 指定文字コードで開く方法

  • C-x RET r

3.8 カーソルの列の固定/解除方法 [2015-01-04]

3.9 コマンドラインでの使い方

  • 使用例:
    • emacs –batch -q –no-site-file -L /usr/local/share/emacs/23.4/lisp/org/ -l org –visit 2013-09-16-140123.howm -f org-mode –funcall org-export-as-html-batch
    • オプションの意味
      –batch バッチモードで起動する
      -q バッチモードで起動する
      –no-site-file いろいろなものをロードしない
      -L path を load-path に追加する
      -l org org モードをロードする (* -f org-mode と被っていないか?)
      –visit バッファにファイルをロードしておく
      -f org-mode org-mode をロードする
      –funcall M-x で呼び出す関数名を指定する
  • 僕の環境だと,自分の .emacs を読み込まないと動作しない
    • % emacs --batch -q -l ~/.emacs --visit='2013-09-21-134220.howm' --funcall 'org-export-as-html-batch'

3.10 モードの確認

M-x describe-mode 

3.11 センタリング

M-x center-region 

3.12 emacs 内から指定ディレクトリ以下をバイトコンパイル

C-u C-0 M-x byte-recompile-directory

3.13 コマンドラインからバイトコンパイル

% emacs -batch -f batch-byte-compile /path/to/*.el

zsh なら,以下のようなディレクトリ指定も可能.

% emacs -batch -f batch-byte-compile /path/to/**/*.el

3.14 指定文字コードで開く方法

  • C-x RET r
    • 文字化けしたまま開いて、上記コマンド。ファイルを revert するかと聞かれるので、yes。
  • Esc + x set-buffer-file-coding-system

3.15 .el を .elc にバイトコンパイルする方法

  • コマンドライン
    • % emacs -batch -f batch-byte-compile *.el
  • emacs 内で単体ファイルをやる方法
    • M-x byte-compile-file
  • emacs 内で大量のファイルをやる方法
    • M-x byte-recompile-directory
      これで、指定したディレクトリ以下が全てコンパイルされる。
  • 参考

4. 自作機能

4.1 今開いているファイルまたはフォルダをエクスプローラで開く

[2023-12-13] WSL1 環境において emacs を利用中,Windows 側のファイルをいじることがあるが,そのファイルがあるフォルダをエクスプローラで開きたい時がある.たとえば,org-mode で文章を書いていて,PDF export した時,PDF を Acrobat など Windows 側のアプリで見る,など.

以下のコードで,今開いているフォルダを開くならば,C-c C-d o,ファイルを Windows アプリで開きたい場合は,C-c C-d f で開ける.

(global-set-key "\C-c\C-do" 'open-current-file-dir-with-explore)
(global-set-key "\C-c\C-df" 'open-current-file-with-explore)
 
;; (set-drvfs-alist) は,https://www49.atwiki.jp/ntemacs/pages/74.html 参照.
(defun set-drvfs-alist ()
  (interactive)
  (setq drvfs-alist
        (mapcar
         (lambda (x)
           (when (string-match "\\(.*\\)|\\(.*?\\)/?$" x)
             (cons (match-string 1 x) (match-string 2 x))))
         (split-string (concat
                        ;; //wsl$ or //wsl.localhost パス情報の追加
                        (when (or (not (string-match "Microsoft" (shell-command-to-string "uname -v")))
                                  (>= (string-to-number (nth 1 (split-string operating-system-release "-"))) 18362))
                          (concat "/|" (shell-command-to-string "wslpath -m /")))
                        (shell-command-to-string
                         (concat
                          "mount | grep -E 'type (drvfs|cifs)' | sed -r 's/(.*) on (.*) type (drvfs|cifs) .*/\\2\\|\\1/' | sed 's!\\\\!/!g';"
                          "mount | grep 'aname=drvfs;' | sed -r 's/.* on (.*) type 9p .*;path=([^;]*);.*/\\1|\\2/' | sed 's!\\\\!/!g' | sed 's!|UNC/!|//!' | sed \"s!|UNC\\(.\\)!|//\\$(printf '%o' \\\\\\'\\1)!\" | sed 's/.*/echo \"&\"/' | sh")))
                       "\n" t))))
 
(set-drvfs-alist)
 
(defun my-action-lock-explorer-open (url)
  (message (concat "/select,/root," url))
  (start-process "explorer" "my-process" "/mnt/c/Windows/explorer.exe" (concat "/root,/select," url)))
(setq action-lock-default-rules
      (cons (action-lock-general 'my-action-lock-explorer-open
                                 "open://\\(.*\\)$"
                                 1)
            action-lock-default-rules))
 
(defun unix-path-to-windows (unix-path)
  "WSL unix path を windows に変換する"
  (let ((unix-path-prefix "")
        (ret ""))
    (mapcar (lambda (x)
              (setq unix-path-prefix (car x))
              (unless (string= unix-path-prefix "/")
                (if (string-match unix-path-prefix unix-path)
                    (setq ret (concat 
                               (cdr (assoc unix-path-prefix drvfs-alist))
                               (replace-regexp-in-string "/" "\\\\"
                                                         (replace-regexp-in-string
                                                          unix-path-prefix "" unix-path )))))))
            drvfs-alist)
    ret))
 
(defun open-current-file-dir-with-explore ()
  (interactive)
  (my-action-lock-explorer-open (unix-path-to-windows (file-name-directory buffer-file-truename))))
 
(defun open-current-file-with-explore ()
  (interactive)
  (my-action-lock-explorer-open (unix-path-to-windows buffer-file-truename)))

5. Windows における emacs [2023-09-08]

5.1 概要

Windows における emacs を利用は,いくつか方法がある.

  1. Windows バイナリを gnu.org からダウンロードする
  2. WSL1/WSL2 で emacs をインストールし,X で飛ばす.

Windows バイナリも単なるテキストエディタとしてのみ利用するなら問題無いが,長い org-mode の文章の folding/unfolding をした際に動作が不安定になる点や,例えば org-mode の PDF 生成のために LaTeX コマンドを emacs から呼び出すように外部コマンドと連携する際,いちいちコマンドの手配を Windows として行なわなければならないなど,様々な機能を使おうとすればするほど,環境構築が手間になる.Unix 環境で使われることを想定して作られているので,仕方がないのかもしれない.

Unix 系 OS の操作に慣れているならば,WSL に emacs をインストールして,WSL1 なら普通に Window を出すか,WSL2 なら X を飛ばす,というやりかたの方が環境構築の手間は大幅に軽減される為,僕は WSL1/2 が出てからは WSL1/2 の emacs のみを利用している.

5.2 WSL1 か WSL2 か

WSL1 と WSL2 の差については,他の記事が色々あるのでそちらを参照してもらえれば.

大雑把には,WSL1 は Linux システムコールやファイルシステムを Windows のアプリケーションとして動作するようにエミュレーションしていて,カーネル自体は動作していない.WSL1 のアプリケーションは一つ一つが Windows のアプリケーションとして Windows に認識されている.タスクマネージャを見ていると,実行しているコマンド一つ一つがプロセスとして表示させる.Linux のファイルシステムへのアクセスは DrvFS というエミュレーションの仕組みが導入されており,大変遅い代わりに,NTFS 側へのアクセスは問題無く行なえる.また,GUI の表示も X を飛ばすわけではなく Windows のアプリとして動作するので,安定した動作になる.

WSL2 は(多分) hyper-v による VM 技術をベースにして,OS をまるごとエミュレーションしている.WSL2 ホストにアクセスする際は,内部の仮想ネットワークによって実現される.それぞれ実行してるプロセスも Windows 側からは VM のプロセスが動いているという認識のみになる.よって,Linux のファイルシステムへのアクセスは WSL1 と比べると圧倒的に速い.ただし,Wnidows 側のファイルシステムへのアクセスは samba で NTFS をマウントしたような形になりパーミッション問題が発生しやすい.例えば,svn checkout を /mnt/c/ 以下で行なおうとすると permission error が発生して失敗する.逆に Windows 側から Linux のファイルシステムへアクセスする際も一定の制限が存在する.また,Windows 側に GUI を出す場合でも X を飛ばさなければならない.

利用用途によって WSL1 と WSL2 の選択は変わってくる.例えば Web 開発を行なうならば,ローカルで Web サーバを立ち上げたりする際に,サーバとかなり似た環境を構築できる WSL2 の方が適しているし,WSL2 の Linux と Windows ホストの間でファイルのやりとりが発生する頻度も低い為,WSL2 の構造による制限はあまり問題にならなくなる.

逆に,emacs を利用してメールを書いたり,メモをとったりするような,生活環境として利用する場合は,Windows とのデータのやりとりがスムーズに出来ることや,X を飛ばすというオーバヘッドのある GUI の表示はあまり便利でない.ファイルのオープンの際にドラッグ&ドロップも出来ないし,ノート PC でスリープ→復帰をした際に,X で飛ばしていた emacs が消えることもある.よって,生活環境としては WSL1 の方が適しているのでは,と思う.

ただし,[2023-09-09] 現在,WSL1 がどこまで活発に開発・メンテナンスされているのかあまり調べていないが,ここなどを見ると,積極的に捨てる気はないが,対してメンテナンスもしてない,という状況に見えていて,そのうち WSL1 がなくなることもありうると感じる.

5.3 WSL1

  • ファイルシステムが遅いから,howm モードの起動が遅い.

5.4 WSL2

5.4.1 インストール

  • WSL2 のインストール方法は別サイト参照.
  • emacs のインストールも,別サイト参照.
    • apt などパッケージで入れても良い.
    • emacs 28 から登場した native compilation は,elisp の動作が 100 倍レベルで速くなるそうなので,もし利用している OS のパッケージが 28 になっていなかったら,自分でコンパイルして入れても良いかもしれない.

参考:

% sudo apt-get install libwebkit2gtk-4.0-dev libgccjit-8-dev
% CPPFLAGS='-I/usr/lib/gcc/x86_64-linux-gnu/8/include' CFLAGS='-L/usr/lib/gcc/x86_64-linux-gnu/8' ./configure --with-native-compilation --with-xwidgets --prefix=$HOME/emacs29 --with-imagemagick

5.4.2 X の設定

WSL2 環境での X は,WSLg というものが存在している.[2022-06-01] 時点では,例えば Windows の拡大縮小で 150% としているように,DPI を変更している場合に,X 側でのスケールの変更に対応していない.Xming などの Xserver は対応している為,WSLg を無効化し Xming を利用するものとする.

  • WSLg の無効化
    • C:\Users\<yourUserName>\ に,.wslconfig というファイルを作成し,以下を記述.
      • [wsl2]
        guiApplications=false
    • ubuntu を再起動(参考):
      • powershell などを起動
      • 対象となるディストリビューションを確認
        • > wsl -l
      • 終了させる
        • > wsl -t Ubuntu-16.04
      • 自動的に再起動してくる.
  • Xming をインストール.
    • Windows11 の場合,起動する際,Extra settings において,Disable access control というチェックボックスがあるので,必ずこれにチェックを入れる.
    • このままだとまだ少しぼやけるので,C:\Program Files\VcXsrv の中にある xlaunch.exe のプロパティを開き,互換性タブの中の,高 DPI 設定の変更を開く.次に,高 DPI スケール設定の上書き,の中のチェックボックスにチェックを入れ,「アプリケーション」を選択する.

5.4.3 DISPLAY 環境変数の設定

ターミナルから X を飛ばす際,WSL2 Linux 側で DISPLAY 環境変数を設定する必要があるが,Windows11 の場合,ホスト OS の Windows 側の IP が固定されていないため,以下のような記述方法で指定する.

% export DISPLAY=`hostname`.mshome.net:0.0

5.5 フォントの設定

5.5.1 概要

WSL は Windows と統合されており,TrueType や OpenType のフォントを利用できるので,Windows に入っているものを利用した方が楽.

WSL1/2 どちらでも同じ設定が可能なはず.

5.5.2 設定方法

ほぼ,こちら のサイトの通りに行なった.

まず,Windows 側のフォントフォルダへのシンボリックリンクを作成する.

% sudo ln -s /mnt/c/Windows/Fonts /usr/share/fonts/windows

Ubuntu 側のフォントデータの作成を行なう.

% sudo fc-cache -fv

使用可能フォントの一覧は以下のコマンドで調べられる.

% fc-list

[2023-10-04] フォントを Windows 側にインストールする場合は,「全てのユーザに対してインストール」にしないと,/mnt/c/Windows/Fonts に出てこないので注意.また,白源フォントが最近流行っていると聞いたので変更してみた.とても見やすい気がする.また,以下の設定も,少し変更.

5.5.3 フォント

[2023-11-05] [2023-10-12] [2023-09-10]

org-table なども利用することを考えると,等幅フォントを利用するのが良い.MS-Gothic でもいいのかもしれないが,折角なので色々試してみている.

等幅という意味でしっかりしているのは,Ricty Diminished.ただし,emacs 上でハイライトなどをした時に少しフォントの高さが変わる(bold にした際の文字の高さなどが何かおかしい?)ことがあるので,通常の表示は白源フォントを利用している.ただし,白源フォントは org-table での表示が崩れることがある.良く分からないが,カーニングとかフォント内の微妙な情報がおかしいのかな?ということで,org-table だけ Ricty Diminished を使うようにしている.

(set-frame-font "HackGen35 Console NF-15")
 
(set-fontset-font t 'japanese-jisx0208 (font-spec :family "HackGen35 Console NF-15")) 
;; [2023-10-05] これをやらないと、| で日本語変換を始めた時に,markdown-table-face が設定されてなくて,変なフォントになる.
;;(add-to-list 'default-frame-alist '(font . "HackGen35 Console NF-15"))
(set-face-attribute 'fixed-pitch nil :font "HackGen35 Console NF-15")
 
(custom-set-faces
 '(org-table ((t (:inherit fixed-pitch :foreground "LightSkyBlue" :family "Ricty Diminished")))))

【追記】

少し前から,→や○のフォントが少し小さくておかしいな?と思っていた.白源フォントを設定したのになんでここだけこんな変なんだ?白源がおかしいのか?と思って,→や○の上で C-u C-x = としてフォントを確認してみたところ,白源ではないフォントになっていた.おかしいな…と思って良く眺めていたら,→○は script: symbol と書いてあり,symbol な文字だけフォントが別なことに気づいた.もう少し調べてみたところ,次の StackExchange の記事を発見.

https://emacs.stackexchange.com/questions/62049/override-the-default-font-for-emoji-characters

(setq use-default-font-for-symbols nil)

を設定しないと,何がなんでもデフォルトフォントを使うようになっているとのことが判明.どこかのサイトの人が,emacs の C のコア部分まで読んでみたところ,コア内部で指定されている値とのこと.なぜだ…

また,fontset の指定は emacs が起動してしばらくしてからじゃないと反映されなかったので,afte-init-hook のタイミングで指定するようにしている.もしかしたらここは,今後設定を見ていく中で必要なくなるのかもしれない.

(add-hook 'after-init-hook
	(lambda ()
	  (set-fontset-font t 'symbol (font-spec :name "Ricty Diminished"))))

5.6 WSL emacs と Thunderbird との連携

5.6.1 概要 [2023-09-12]

仕事でのメール環境を構築する場合,添付ファイルのスムーズな処理が必須になる.Mew や Wanderlust などの emacs の MUA を利用しても添付ファイルの処理は出来るが,ファイル名の文字コード問題や,いちいち一度ファイルをローカルに保存してから開く,などをやっていると,どうしても対応が遅くなってしまう.

メールの基本的な送受信や添付ファイルの処理は,GUI なメールソフトに任せ,メール編集のみ emacs にするのが個人的にはしっくりきている.GUI なメールソフトで 2023 年の時点でメジャーなのは,Thunderbird や Outlook な気がしている.世の中では Gmail を使う人も多い.メール編集を emacs で行なうためには,編集時点で外部エディタと連携できなければならず,Thunderbird と Gmail で textarea を emacs に飛ばす方法になる. 僕はクラウドサービスに頼り切るのは,いつクラウド運営社によって方針が変更されるか分からず,あまり好きではないので,Thunderbird と外部エディタプラグインを利用している.

Thunderbird 68 までは,external editor というプラグインが利用可能だった.このプラグインは,プラグインのみの単体で動作していた.Thundirbird 69 になった時点で Thunderbird 内部のプラグイン実行環境が変わったようで,作者が更新の時間をとれなくなり,更新が止まってしまった.多分一から書き直さなければならず,やる気が無くなってしまったのだと思われる.業を煮やした別のユーザが,external editor revivedという,全く別の方式による外部エディタとの連携プラグインを作成した.現状ではこちらが安定して動いている.詳細はインストール方法に記載するが,Native Messaging Host という Windows の機能を,bash を利用して利用する仕組みになっており,Windows では cygwin のインストールが必須になる.Mac や Linux の Thunderbird の場合,もう少し環境構築が楽かもしれない.業を煮やしたユーザの環境が Linux だったため,このような仕組みを採用したような気もするし,この方法が実現には楽だったのかもしれないが細かい中身までは追ってないので設計方針までは分かっていない.(ここらへんから sven765 さんの活動がスタートし,Linux での環境が整っていたが,ここで Frederick888 さんが颯爽と全 OS に対応した形でプラグインを公開している.ソフトの方向性が似ているので,sven765 さんのを見つつ,Frederick888 さんが整備し切ったように見える.)

Thunderbird と emacs が同じ OS に入って入ればインストールマニュアルに書かれているのと同じように設定していけば問題ないが WSL emacs と Windows 側にインストールされている Thunderbird を連携させる場合は,もう少し設定が必要になる.

5.6.2 emacsclient の利用設定

Thuderbird から WSL emacs を emacsclient 経由で呼び出す為には,Windows 側に Windows バイナリの emacs を入れておき,その中にインストールされている emacsclient を利用して WSL emacs にデータを渡す.デフォルトでは emacsclient と emacs 側のデータのやりとりは local socket と呼ばれる方法を利用する模様(参考:emacsclient の manualの中の –server-file の部分).local socket というのが僕は分からないけど,Unix Domain Socket のことだと思われる.もしそうだとすると,同一ホスト内でのやりとりしか出来ないし,Windows だと対応していなかったりするので,TCP を使ったやりとりにするのが良い.

TCP を利用する為には,WSL emacs 側で以下のような設定を行なう.

;; ------------------------------------------------------------------------
;;emacsclient 向け server mode 設定
;; ------------------------------------------------------------------------
 
(and (> emacs-major-version 24)
     (string= windowmode "window")
     (defun server-ensure-safe-dir (dir) "Noop" t)
     (add-hook 'mail-mode-hook
	       (lambda ()
		 ;; C-x # を変更する (server-edit)
		 ;; http://www.emacswiki.org/emacs/MuttInEmacs#toc5
		 (define-key mail-mode-map [(control c) (control c)]
		   (lambda ()
		     (interactive)
		     (save-buffer)
		     (server-edit)
		     (elscreen-kill) 
		     ))))
 
     ;;(defun server-ensure-safe-dir (dir) "Noop" t)
     (defadvice server-ensure-safe-dir (around
					my-around-server-ensure-safe-dir
					activate)
       "Ignores any errors raised from server-ensure-safe-dir"
       (ignore-errors ad-do-it))
 
     ;; TCP による emacsclient 設定 [2019-01-04]
     ;; https://www.gnu.org/software/emacs/manual/html_node/emacs/TCP-Emacs-server.html
     ;; emacsclientw.exe --server-file="C:\Users\skk\emacsserver\server"
     ;; 下記の 「WSL 連係のための設定」も必須.
     (setq server-use-tcp t)
 
     (cond ((string-match (getenv "HOSTNAME") "skk-T14")
	    (setq server-host (shell-command-to-string "ip -4 addr show eth0 | grep 'inet' | awk '{print $2}' | cut -d/ -f1 | tr -d '\n'")))
	    (t (setq server-host "localhost")))
 
     (setq server-port 12345)
     (setq server-auth-dir "/mnt/c/Users/skk/emacsserver/")
 
     (server-start)
 
     ;(server-done)
 
     ;; client 越しの処理が終ったら、タブを消す
     (add-hook 'server-done-hook 'elscreen-kill) 
)

HOSTNAME で IP とってる部分は,適宜自分の環境に書き替える.

server-auth-dir で指定しているディレクトリに,アドレスやポートに関する情報が書かれた server というファイルが保存される.中身は以下のような内容になっている.

172.30.191.189:12345 5313
R`n=H?QevWW{AB<lj<~'5>z!!o`[Lpiv?F%/d~ww\*p%o$aE5=D!hsvB&s4U~BIa%

Windows 側で動かす emacsclient は,GNU Emacs のサイトからダウンロードしてくる.多少バージョンが異なっていても,多分問題無く動作する.

また,以下の設定も必要.WSL2 などの最新情報は,https://www49.atwiki.jp/ntemacs/pages/74.html ここがもっとも情報が集まっているので参考にすると良い.

;;------------------------------------------------------------------------
;; WSL 連係のための設定
;; [2022-06-27]
;; WSL2 のためのコピー.
;; [2019-09-30]
;; 「Windows 10 1903 になって、/mnt/c のマウントパスが C: から C:\ に変更となったので、その対策を行いました」
;; ということで,関数をコピペしなおし.
;; https://www49.atwiki.jp/ntemacs/pages/74.html
;; [2019-01-04]
;;------------------------------------------------------------------------
;; (require 'cl-lib)
 
;;; [2022-06-27] コピペ
(defun set-drvfs-alist ()
  (interactive)
  (setq drvfs-alist
        (mapcar (lambda (x)
                  (when (string-match "\\(.*\\)|\\(.*?\\)/?$" x)
                    (cons (match-string 1 x) (match-string 2 x))))
                (split-string (concat
                               ;; //wsl$ or //wsl.localhost パス情報の追加
                               (when (or (not (string-match "Microsoft" (shell-command-to-string "uname -v")))
                                         (>= (string-to-number (nth 1 (split-string operating-system-release "-"))) 18362))
                                 (concat "/|" (shell-command-to-string "wslpath -m /")))
                               (shell-command-to-string
                                (concat
                                 "mount | grep -E 'type (drvfs|cifs)' | sed -r 's/(.*) on (.*) type (drvfs|cifs) .*/\\2\\|\\1/' | sed 's!\\\\!/!g';"
                                 "mount | grep 'aname=drvfs;' | sed -r 's/.* on (.*) type 9p .*;path=([^;]*);.*/\\1|\\2/' | sed 's!\\\\!/!g' | sed 's!|UNC/!|//!' | sed \"s!|UNC\\(.\\)!|//\\$(printf '%o' \\\\\\'\\1)!\" | sed 's/.*/echo \"&\"/' | sh")))
                              "\n" t))))
 
(set-drvfs-alist)
 
(defconst windows-path-style-regexp "\\`\\(.*/\\)?\\([a-zA-Z]:\\\\.*\\|[a-zA-Z]:/.*\\|\\\\\\\\.*\\|//.*\\)")
 
(defun windows-path-convert-file-name (name)
  (setq name (replace-regexp-in-string windows-path-style-regexp "\\2" name t nil))
  (setq name (replace-regexp-in-string "\\\\" "/" name))
  (let ((case-fold-search t))
    (cl-loop for (mountpoint . source) in drvfs-alist
             if (string-match (concat "^\\(" (regexp-quote source) "\\)\\($\\|/\\)") name)
             return (replace-regexp-in-string "^//" "/" (replace-match mountpoint t t name 1))
             finally return name)))
 
(defun windows-path-run-real-handler (operation args)
  "Run OPERATION with ARGS."
  (let ((inhibit-file-name-handlers
         (cons 'windows-path-map-drive-hook-function
               (and (eq inhibit-file-name-operation operation)
                    inhibit-file-name-handlers)))
        (inhibit-file-name-operation operation))
    (apply operation args)))
 
(defun windows-path-map-drive-hook-function (operation name &rest args)
  "Run OPERATION on cygwin NAME with ARGS."
  (windows-path-run-real-handler
   operation
   (cons (windows-path-convert-file-name name)
         (if (stringp (car args))
             (cons (windows-path-convert-file-name (car args))
                   (cdr args))
           args))))
 
(add-to-list 'file-name-handler-alist
             (cons windows-path-style-regexp
                   'windows-path-map-drive-hook-function))

5.6.3 external editor revived のインストール

基本的に,Wiki に全てのインストール方法は記載されていて,Windows のインストールマニュアルを見つつ進めると良い.

1)https://addons.thunderbird.net/en-GB/thunderbird/addon/external-editor-revived/ こちらから,.xpi ファイルを落してきて,Thunderbird にインストールする.

2)Cygwin をインストールする.bash が使えればよいので,他に Cygwin を利用していなければ,インストーラの時点で最小構成にすればよい.2022-11 時点で最小インストールをして,148MB だった.ちなみに,bash.exe を利用して実現できないのか試してみたが,少なくとも僕はうまく行かなかった.→ 自分の PC で作業した痕跡が見つからなかったので,記憶違いかもしれない.

3)https://github.com/Frederick888/external-editor-revived/releases ここから,windows-latest-msvc-native-messaging-host-v0.6.0.zip というファイルを DL する.バージョン番号は作業時点での最新版をみること. 解凍すると,external-editor-revived.exe という実行ファイルがあるので,WSL でもコマンドプロンプトでもよいので実行する.WSL 上で実行する場合,マニュアルにあるような chmod による実行権の付与は必要ない.実行すると以下のような内容が出力されるので,.json として,どこかに保存する.

Please create 'external_editor_revived.json' manifest file with the JSON below.
Consult https://wiki.mozilla.org/WebExtensions/Native_Messaging for its location.
 
{
  "name": "external_editor_revived",
  "description": "Edit emails in external editors such as Vim, Neovim, Emacs, etc.",
  "path": "C:\\Users\\skk\\software\\external-editor-revived\\external-editor-revived.exe",
  "type": "stdio",
  "allowed_extensions": [
    "external-editor-revived@tsundere.moe"
  ]
}

4)レジストリエディタを開き,HKEY_CURRENT_USER\Software\Mozilla\NativeMessagingHosts まで進む.この中に,external_editor_revived というキーを作成し,上記 json ファイルの保存パスをデータとして保存する.

5)Thunderbird のメニューの,アドオンとテーマから,External Editor Revived を選び,オプションに以下を指定する.

  • shell:
    C:\cygwin64\bin\bash.exe
  • Command template:
    C:/cygwin64/bin/mintty.exe --window hide --exec C:/Users/skk/software/emacs-28.1/bin/emacsclient.exe --server-file=C:/Users/skk/emacsserver/server /path/to/temp.eml

--server-file は,emacsclient の利用設定で指定したディレクトリを,Windows のフォルダ表現に変更して指定する.

多分これで動作するはず.

5.7 メールをかきはじめる行への移動

[2023-11-08] [2023-11-01]

External Editor Revived 経由で,emacs でメールを開くと mail-mode でメールを開き,以下のようなテキストが出てくる.このとき,カーソル位置が 題名 の位置にある.5 行ほど下に移動しなければならず,ちょっとめんどくさい.

External Editor の場合:

題名:   
To:        
Cc:        
Bcc:       
Reply-To:  
-=-=-=-=-=-=-=-=-=# この行は削除しないでください。 #=-=-=-=-=-=-=-=-=-

External Editor Revived の場合:

From: Sakakibara Hiroshi <sakakibara.hiroshi@bpsinc.jp>
To: Sakakibara Hiroshi <skk@bpsinc.jp>
Cc:
Bcc:
Reply-To:
Subject: Re: test
X-ExtEditorR-Send-On-Exit: false
X-ExtEditorR-Help: Use one address per `To/Cc/Bcc/Reply-To` header
X-ExtEditorR-Help: (e.g. two recipients require two `To:` headers).
X-ExtEditorR-Help: KEEP blank line below to separate headers from body.

mail-mode-hook に,以下のように指定しておけば,編集行に移動してくれると思う.

(defun skk/goto-beginning-of-line ()
  (goto-char (point-min))
  (if (re-search-forward
       "\\(-=-=-=-=-=-=-=-=-=# この行は削除しないでください。\\|^$\\)"
       nil
       t)
      (progn
        ;;(message (format "match-end: %s" (match-end 0)))
        ;;(message (format "match-data: %s" (match-data)))
        ;;(goto-char (match-end 0))
        (forward-line 1)))
  )
 
(add-hook
  'mail-mode-hook
   (lambda ()
     (skk/goto-beginning-of-line)))

ただしこのコードだと,一度編集した後に,もう一度 External Editor Revived で emacs に飛ぶと,少しずれてしまう.原因は現状分からない...

5.8 External Editor Revived でメール編集終了時にすぐにメールを送る

[2023-11-08]

github の wiki の Send on Exit の節を見ると,ヘッダー部分の X-ExtEditorR-Send-On-Exit: falsetrue にしたら,Thunderbird で即座に送信してくれる,と書いてある.

ならば,C-c C-c でメール編集を終了した時に,送信するかどうかを尋ね,送信の場合は true にしてしまえば良い.

上のほうの emacsclient の利用設定 の該当部分を以下のように追記した.

(define-key mail-mode-map [(control c) (control c)]
            (lambda ()
              (interactive)
 
              (goto-char (point-min))
              (if (and (re-search-forward "X-ExtEditorR-Send-On-Exit:" nil t)
                       (y-or-n-p "send now?"))
                  (progn
                    ;;(goto-char (match-end 0))
                    (beginning-of-line)
                    (setq tmp (buffer-substring-no-properties (point) (line-end-position)))
                    (setq tmp (replace-regexp-in-string "false" "true" tmp))
                    (delete-region (point) (line-end-position))
                    (insert tmp)))
 
 
              (setq windeactivate (shell-command-to-string (format "back_to_win.exe %s" winactivate)))
              (save-buffer)
              (server-edit)
              ;;(elscreen-kill)
              ))

goto-char は多分必要ない.if の部分を,メールを送信する寸前で適用してやれば良い.

6. elisp

Emacs の拡張用言語である Emacs Lisp についてと,Emacs の挙動について気になった部分を少しメモする.

6.1 関数のオプション引数 [2023-11-02]

参考元からコピー.nil が返ってくる.

;; aは必須引数で、bとcは省略可能関数(&optionalをつける)
(defun optional-arg (a &optional b c)
  (setq c (or c 20))			; デフォルト引数もどき
  (list a b c))
(optional-arg 1)			; => (1 nil 20)
(optional-arg 1 2)			; => (1 2 20)
(optional-arg 1 2 3)			; => (1 2 3)
(optional-arg 1 2 3 4)			; => エラー

参考:

6.2 データ構造 [2023-11-02]

<TBD>

emacs のデータ構造は,どうも C の内部実装に引っ張られてる気がしないでもない.linked list などを把握している人は,理解しやすいのかもしれない.さっと見たところ,そう理解したが,まだ完全には理解できてない.

  • リスト
    • 配列.
    • dolist のようなループを使って取り出されることも多い.car/cdr も良く使われる.
  • アソシエーションリスト
    • 連想配列.
    • assoc 関数などを利用して検索してデータを取り出せる.
    • car/cdr で取り出すのも可能.良く使われる方法.

参考:

6.3 データの sort [2023-11-02]

sort 関数を利用して,リストや association list のソートが可能.

(setq hoge '((sym . (4 5))
 (sym . (1 3))
 (sym . (7 9))
 (sym . (11 15))))
 
(sort hoge (lambda (a b) (< (car (cdr a)) (car (cdr b)) )))
(setq all-block '((blockquote (3 4)) (ul (8 9)) (blockquote (13 14)) (codeblock (18 19)) (ul (23 24))))
(setq all-block  (sort all-block (lambda (a b) (< (car (car (cdr a))) (car (car (cdr b))) ))))

参考:

6.4 起動時の処理シーケンス [2023-10-20]

scratch-log の部分で少し触れたが,起動シーケンスを理解しておかないと,起動時に読み込む mode などで失敗することがある.

42.1.1 Summary: Sequence of Actions at Startup に,起動シーケンスのまとめが書いてある.

上記 scratch-log の場合,当初,行のハイライト設定を after-init-hook に書いていて,問題なく使えていたのだが,*scratch* バッファのデフォルトモードを org-mode にしたくなり,initial-major-mode に org-mode を設定したところ,ハイライト設定が動作しなくなっていた.これは,起動シーケンスの 9. が after-init-hook で,inital-major-mode が 19. だったため,org-mode の設定でハイライト設定が上書きされてしまっていたようである.

現在は,26. の emacs-startup-hook でハイライト設定を指定したので,動作している.

参考:

6.5 実行

  • バッファの中で,好きなところに elisp を記述し,elisp の最終行で以下.
    • Ctrl-c Ctrl-e → 結果は Window 下部に表示される
    • Ctrl-u Ctrl-c Ctrl-e → 結果がバッファ内に挿入される.

6.6 分岐

if と cond がある.cond は switch 文に似ている.

文法:

(cond (条件 true時に実行するコード)
      (条件 true時に実行するコード))

以下は,HOSTNAME がマッチした場合と,すべてを拾うフォールバックが書かれている.

 (cond ((string-match (getenv "HOSTNAME") "skk-T14")
      (setq howm-directory "/home/skk/svn/howm"))
      (t (setq howm-directory "/mnt/c/Users/skk/svn/howm")))

if の方が見やすいかもしれない.if <条件式> <then 式> <else 式>のように記述.then 式などで複数の式を書きたい時は,progn でまとめる.ちなみに,lambda ではまとめられない.理由が分からない.

(setq hoge "baka")
(if (string-match hoge "baka")
    (progn 
      (message "this is")
      (message "BAKA"))
    (message "this is not baka"))

6.7 外部プロセス実行 [2023-10-12] [2023-10-06]

外部のプロセスを呼ぶための関数たち.

方法1:

(start-process "emacsが管理するためのサブプロセス名" "サブプロセスが使うバッファ名" "コマンド" "オプション(可変長)")

方法2:変数 hoge に pwd の実行結果を代入する.

 (setq hoge (shell-command-to-string "pwd"))

これらの他にも,emacs とは非同期にプロセスを呼び出すための make-process や,buffer 内の文字列をコマンドの標準入力に渡しつつ実行できるshell-command-on-regionなど,様々な外部コマンド実行方法が存在する.

参考:

6.8 buffer 内を検索する [2023-11-02]

re-search-forward を利用する.マッチした場合,match-start/match-end 関数を利用して,どこがマッチしたかの情報が得られる.buffer 内をすべて検索したい場合は,re-search-forward が nil を返すまで while を利用する.

(if (re-search-forward "# この行は削除しないでください。 #")
    (progn
      (goto-char (match-end 0))
      (forward-line 1)))

参考:

6.9 文字列置換 [2023-10-06]

今編集しているファイル名(buffer のファイル名)の /path/to/hoge.md を,/path/to/hoge.pdf に置換する関数.

(replace-regexp-in-string "\\\.md" ".pdf" (buffer-file-name))

参考:

6.10 その他 [2023-10-19]



7. その他未分類

7.1 tags

関数の index をつくってくれる.タグジャンプや関数名補完の際に利用.

% etags *.[ch]

make 時に呼ぶと手間が少ない.

7.2 jdee

  • プログラムを最初に作成する
M-x jde-gen-buffer
Template: (下のリストから適当なものを選択)
 Possible completions are:
 Class                              Console
 Exception Class                    Interface
 Swing App                          Unit Test
Extends?, Implements? などが聞かれるので,必要なら入力.
  • コンパイル
C-c C-v C-b
  • エラー行に飛ぶ
C-x `
  • メソッド補完
C-c C-v C-z
  • インタフェースを実装するコードの生成(implements で必要とされるメソッド)
C-c C-v i
KeyListener
  • System.out.println 出力
C-c C-v C-l

このページへのアクセス 今日: 5 / 昨日: 7 総計: 2640

emacs.txt · 最終更新: 2025/01/13 01:28 by skk
文書の先頭へ
Driven by DokuWiki Recent changes RSS feed Valid CSS Valid XHTML 1.0