由于包括 Emacs 在内的众多编辑器和 IDE 的内嵌终端都在边缘情况有各种各样的问题,我都是拉起一个真正的终端来用,正好少一个 buffer 也不用调整 Emacs 的 buffer 布局。(cURl 的作者 daniel 也是用 Emacs + 外部终端写代码的)
对我来说,拉起一个外部终端只有一个场景满足不了,那就是 REPL,准确来说是用 C-x C-e
执行一小段代码。
研究了一下,发现 KDE 的 konsole 有一套从上古时代就支持的 dbus API。只需几行 elisp 就可以直接向 Konsole 发送命令。
演示
原理
Konsole 启动会创建 dbus service → org.kde.konsole
每个创建的窗口对应一个 object path → /Windows/{1,2,3,4...}
每个窗口中的标签对应一个 object path → /Sessions/{1,2,3,4,5...}
主要用到的 method → org.kde.konsole.Session
的 sendText
和 runCommand
代码
最终会暴露出几个命令
-
konsole-new-session-for-buffer
用当前 buffer 的目录打开一个新的标签 -
(konsole-run-cmd-in-active-session str)
发送 str 到 Konsole 活跃的标签 -
konsole-run-line
发送当前行
;;; -*- lexical-binding: t; -*-
(require 'dbus)
;; dbus object paths
(defvar konsole-window-path "/Windows/1")
(defvar konsole-mainwindow-path "/konsole/MainWindow_1")
;; Internals
(defun konsole--get-default-profile ()
(dbus-call-method
:session
"org.kde.konsole"
konsole-window-path
"org.kde.konsole.Window"
"defaultProfile"))
(defun konsole--get-active-session ()
(dbus-call-method
:session
"org.kde.konsole"
konsole-window-path
"org.kde.konsole.Window"
"currentSession"))
(defun konsole--raise ()
"raise window, which may not work on Wayland."
(dbus-call-method
:session
"org.kde.konsole"
konsole-mainwindow-path
"org.qtproject.Qt.QMainWindow"
"raise")
)
;; public
(defun konsole-run-cmd-in-active-session (cmd)
(dbus-call-method
:session
"org.kde.konsole"
(concat "/Sessions/" (number-to-string (konsole--get-active-session)))
"org.kde.konsole.Session"
"runCommand"
cmd))
(defun konsole-run-cmd-between (begin end)
(konsole-run-cmd-in-active-session
(buffer-substring-no-properties begin end)))
;; interactive uses
(defun konsole-new-session-for-buffer ()
"Create a new tab and set its path to current buffer's path"
(interactive)
(dbus-call-method
:session
"org.kde.konsole"
konsole-window-path
"org.kde.konsole.Window"
"newSession"
(konsole--get-default-profile)
(file-name-directory (buffer-file-name)))
(konsole--raise))
(defun konsole-run-region (begin end)
(interactive "r")
(konsole-run-cmd-in-active-session
(buffer-substring-no-properties begin end)))
(defun konsole-run-line ()
(interactive)
(konsole-run-cmd-in-active-session
(buffer-substring-no-properties
(line-beginning-position)
(line-end-position))))
例子, 发送上一个 sexp
(defun +eval-last-sexp-in-konsole ()
(interactive)
(save-excursion
(let* ((end (point))
(begin (progn
(backward-sexp)
(point))))
(konsole-run-cmd-between begin end))))
通过 OCaml 的 tuareg mode 获取指针附近的代码并发送
(defun +eval-tuareg-phrase-in-konsole ()
(interactive)
(let ((phrase (tuareg-discover-phrase)))
(unless phrase
(user-error "Expression after the point is not well braced"))
(let ((begin (car phrase))
(end (cadr phrase)))
(konsole-run-cmd-between begin end))))