EMACS-DOCUMENT

=============>集思广益

将Emacs作为X剪切板管理器

一直以来,我都是使用clipit(parcellite的克隆版本)作为X环境下的剪切板管理器(X clipboard manager). 但是我发现当有一个剪切板管理器运行时,Emacs会出现一个bug, 这逼着我去寻找clipit的替代品. 我如愿以偿地找到了一个名为clipmon的package, 它工作的相对还不错,但是美中不足的是,它也有一个 bug. 而且,看起来在Emacs中监测X剪切板的唯一方式似乎只能由Emacs主动去检查X剪切板中的内容. 我不是很喜欢这种每隔一两秒就由Emacs检查一次X剪切板的方案. 我的替代方案是将检查剪切板的动作移出Emacs,由shell脚本来完成.

#!/bin/sh

occ=$(xclip -sel clip -o | sed 's/[\"]/\\&/g')

while sleep 2; do
    ncc=$(xclip -sel clip -o | sed 's/[\"]/\\&/g')
    if [ "${occ}" != "${ncc}" ]; then
        emacsclient -e "(kill-new \"${ncc}\")"
        occ=${ncc}
    fi
done

这是一个简单而有效的解决方案,但是它依然要不断的探测剪切板的内容. 若能有一个事件驱动的解决方案就好了. 我的Emacs经历让我有股冲动去为X试着写一个on-clipboard-change hook, 但这个工作量就太大了.

一般来讲, 除了Emacs之外,我只用另外两个应用(一个web浏览器,一个终端模拟器),而且这两款应用都是可定制的. 这使得每次我从这些应用中拷贝东西到X剪切板时都可以同时再将剪切板中的内容拷贝到Emacs的kill ring中去. 我用的浏览器是Conkeror,我将下面这段配置添加到它的初始化文件中去了.

function ekr (cc) {
    if (typeof cc === 'undefined') { cc = read_from_clipboard(); }
    cc = cc.replace(/([^\\]*)\\([^\\]*)/g, "$1\\\\$2");
    cc = cc.replace('"', '\\"', "g");
    cc = cc.replace("'", "'\\''", "g");
    var ecc = "emacsclient -e '(kill-new \"" + cc + "\")' > /dev/null";
    shell_command_blind(ecc);
}

interactive(
    "ekr_cmd_copy",
    "Copy the selection to the clipboard and the Emacs kill ring",
    function (I) {
        call_builtin_command(I.window, "cmd_copy", true);
        ekr();
    }
);

undefine_key(caret_keymap,"M-w");
define_key(caret_keymap,"M-w", "ekr_cmd_copy");
undefine_key(content_buffer_normal_keymap,"M-w");
define_key(content_buffer_normal_keymap,"M-w", "ekr_cmd_copy");
undefine_key(special_buffer_keymap,"M-w");
define_key(special_buffer_keymap,"M-w", "ekr_cmd_copy");
undefine_key(text_keymap,"M-w");
define_key(text_keymap,"M-w", "ekr_cmd_copy");

我还在 modules/commands.js 中的 kill-regionkill-ring-save 的命令以及 modules/content-buffer-input.js 中的 cut-to-end-of-line 命令后添加了对 ekr() 的调用. 在 modules/elements.js 中的 copy_text 函数最后添加了对 ekr(text) 的调用.

第二个常用的非Emacs应用是urxvt/tmux. 为了完成跟上面一样的效果,我将下面这行配置加入到 ~/.tmux.conf 中.

bind-key -temacs-copy M-w copy-pipe 'c2e -r'

下面是c2e脚本的源代码.

#!/bin/sh

# c2e: Copy text to the Emacs kill ring.
#
# With no arguments, send the contents of the X clipboard to the Emacs kill ring.
# With -r, first set the clipboard to the contents read from standard input.
# With -s, instead send X primary selection to the Emacs kill ring.

if [ "${1}" = '-r' ]; then
    exec xclip -sel clip -i -f | \
        emacsclient -e "(kill-new \"$(sed 's/[\"]/\\&/g')\")"
elif [ "${1}" = '-s' ]; then
    exec xclip -o | \
        emacsclient -e "(kill-new \"$(sed 's/[\"]/\\&/g')\")"
else
    exec xclip -sel clip -o | \
        emacsclient -e "(kill-new \"$(sed 's/[\"]/\\&/g')\")"
fi

为了使这个新剪切板管理器(即Emacs的 kill ring)更容易使用,我为StumpWM添加了一个新的命令和相应的快捷键.

(defcommand eaacm () ()
  "Emacs as a clipboard manager."
  (run-or-raise "emacsclient -nc" '(:class "Emacs"))
  (run-shell-command "emacsclient -n -e '(let ((helm-full-frame t)) \
      (save-window-excursion (delete-other-windows) (helm-show-kill-ring)))'"))

(define-key *root-map* (kbd "c") "eaacm")

现在不管我正在使用哪个应用,只需要按下 C-t c 就会有一个helm界面让我选择kill ring中的内容. 在Helm中甚至有一项功能可以将当前entry移动到kill ring的顶端同时将该entry的内容拷贝到X剪切板中. 该功能的默认快捷键是 C-c C-k.

偶尔我也会需要在其他应用中拷贝内容到剪切板中,因此我还创建了两个简单的StumpWM命令来调用c2e脚本,并分别分配了快捷键.

(defcommand c2e () ()
  "Copy the X clipboard contents to the Emacs kill ring."
  (run-shell-command "c2e"))

(defcommand s2e () ()
  "Copy the X selection contents to the Emacs kill ring."
  (run-shell-command "c2e -s"))

(define-key *root-map* (kbd "C")        "c2e")
(define-key *root-map* (kbd "s")        "s2e")

仅仅过了几天而已,Emacs就变成了一个很不错的X剪切板管理器了.