在Vim和Emacs中调用GoldenDict查词

GoldenDict是可以把要查询的单词作为参数调用的。如果GoldenDict尚未启动,就会启动并在主界面中显示释义;如果已经启动,就会弹出一个小窗口显示释义。

因此可以在Vim和Emacs中将当前光标所在处单词或者文字选中部分作为参数直接调用GoldenDict。

让deepseek生成代码后,emacs版本直接可用,vim版有bug,经过修改终于能用了。

按了alt+d(meta+d),就会把当前光标处所在单词或者当前选中内容作为参数调用goldendict。

Vim:

" golden_dict.vim
" 查询当前单词/选区并发送到 GoldenDict
function! GoldenDictLookupV()
    " 获取当前模式(更可靠的方式)
    "let current_mode = mode()
    
    " 备份寄存器
    let save_reg = @"
    let save_reg_type = getregtype('"')
    let word = ""
    
    " 根据当前模式获取查询词
"    if current_mode ==# 'v' || current_mode ==# 'V' || current_mode ==# "\<C-v>"
        " 可视模式处理
        silent normal! gvy
        let word = @"
        " 退出可视模式
        silent normal! \<Esc>
"    else
"        " 普通模式处理
"        let word = expand("<cword>")
"    endif    

    " 恢复寄存器
    call setreg('"', save_reg, save_reg_type)

    " 清理单词:去除多余空格和两端符号
    let word = substitute(word, '\_s\+', ' ', 'g')
    "let word = substitute(word, '^\W*\(.\{-}\)\W*$', '\1', '')

    if word == ""
        echohl ErrorMsg | echo "无可用查询内容" | echohl None
        return
    endif

    " 准备系统命令
    let cmd = ""
    if has("win32") || has("win64")
        let cmd = 'start /b goldendict.exe "' . word . '"'
    elseif has("mac")
        let cmd = 'open -a GoldenDict --args "' . word . '"'
    else
        let cmd = 'goldendict "' . word . '" &'
    endif

    " 执行命令(异步)
    silent! call system(cmd)
    echo "正在查询: " . word
endfunction

function! GoldenDictLookupN()
    " 获取当前模式(更可靠的方式)
    "let current_mode = mode()
    
    " 备份寄存器
    let save_reg = @"
    let save_reg_type = getregtype('"')
    let word = ""
    
    " 根据当前模式获取查询词
"    if current_mode ==# 'v' || current_mode ==# 'V' || current_mode ==# "\<C-v>"
"        " 可视模式处理
"        silent normal! gvy
"        let word = @"
"        " 退出可视模式
"        silent normal! \<Esc>
"    else
        " 普通模式处理
        let word = expand("<cword>")
"    endif    

    " 恢复寄存器
    call setreg('"', save_reg, save_reg_type)

    " 清理单词:去除多余空格和两端符号
    let word = substitute(word, '\_s\+', ' ', 'g')
    "let word = substitute(word, '^\W*\(.\{-}\)\W*$', '\1', '')

    if word == ""
        echohl ErrorMsg | echo "无可用查询内容" | echohl None
        return
    endif

    " 准备系统命令
    let cmd = ""
    if has("win32") || has("win64")
        let cmd = 'start /b goldendict.exe "' . word . '"'
    elseif has("mac")
        let cmd = 'open -a GoldenDict --args "' . word . '"'
    else
        let cmd = 'goldendict "' . word . '" &'
    endif

    " 执行命令(异步)
    silent! call system(cmd)
    echo "正在查询: " . word
endfunction

" 设置快捷键
"vnoremap <silent> <M-d> :<C-u>call GoldenDictLookup()<CR><ESC>
"nnoremap <silent> <M-d> :call GoldenDictLookup()<CR>
map <silent> <M-d> :call GoldenDictLookupN()<CR>
vnoremap <silent> <M-d> :<C-u>call GoldenDictLookupV()<CR><ESC>


Emacs:

(defun goldendict-lookup ()
  "查询当前选中的文本或光标下的单词"
  (interactive)
  (let ((word (if (use-region-p)  ; 检查是否有选中区域
                  (buffer-substring-no-properties (region-beginning) (region-end))
                (current-word nil t))))  ; 获取光标下的单词
    (when word
      ;; 清理单词:去除两端空格和内部多余空格
      (setq word (string-trim (replace-regexp-in-string "\\s-+" " " word)))
      
      ;; 处理带空格的词组 (如 "hello world")
      (setq word (shell-quote-argument word))
      
      ;; 针对不同系统构建命令
      (let ((command (cond
                      ;; macOS 系统
                      ((eq system-type 'darwin)
                       (concat "/Applications/GoldenDict-ng.app/Contents/MacOS/GoldenDict-ng " word))
                      
                      ;; Windows 系统
                      ((eq system-type 'windows-nt)
                       (concat "goldendict.exe " word))
                      
                      ;; Linux 和其他系统
                      (t 
                       (concat "goldendict " word)))))
        
        ;; 异步调用 GoldenDict
        (async-shell-command command nil nil))
      (message "正在查询: %s" word))))

;; 绑定到 M-d 快捷键
(global-set-key (kbd "M-d") 'goldendict-lookup)
(global-set-key (kbd "M-o") 'kill-word)

Emacs按了Meta+d会出现一个Async Shell Command。通过以下代码控制async-shell-command产生的窗口行为。以下是两种解决方案:

;在Emacs中,你可以通过自定义display-buffer-alist来控制async-shell-command产生的窗口行为。以下是两种解决方案:
;1. 完全隐藏窗口(后台静默执行)
(add-to-list 'display-buffer-alist
             '("\\*Async Shell Command\\*"
               (display-buffer-no-window)))
;效果:命令执行时不会弹出任何窗口,输出会记录在后台缓冲区(可通过C-x b切换查看)。
;2. 限制窗口高度为两行

;(add-to-list 'display-buffer-alist
;             '("\\*Async Shell Command\\*"
;               (display-buffer-at-bottom)
;               (window-height . 2)))

;效果:
;    窗口固定在框架底部
;    高度限制为两行
;    可滚动查看完整输出(使用C-v/M-v)

赞!我之前都是简单粗暴地处理,需要调用 goldendict 自身的 Ctrl + c + c。

nnoremap <c-c> <esc>muyiw`u
inoremap <c-c> <esc>muyiw`ua
vnoremap <c-c> ygv

有必要用你的代码升级一下了!

vim版本,我做了一点改动,简化一些:

设置快捷键:

nnoremap <silent> <M-d> :call GoldenDict#Lookup('n')<CR>
vnoremap <silent> <M-d> :call GoldenDict#Lookup('v')<CR>

使用 autoload 懒加载:

" autoload/GoldenDict.vim
" 查询当前单词/选区并发送到 GoldenDict
function! GoldenDict#Lookup(mode)
    " 备份寄存器
    let save_reg = @"
    let save_reg_type = getregtype('"')
    let word = ""

    if a:mode == 'v'
        " 可视模式处理
        silent normal! gvy
        let word = @"
        " 退出可视模式
        silent normal! \<Esc>
    else
        " 普通模式处理
        let word = expand("<cword>")
    endif

    " 恢复寄存器
    call setreg('"', save_reg, save_reg_type)

    " 清理单词:去除多余空格和两端符号
    let word = substitute(word, '\_s\+', ' ', 'g')
    let word = substitute(word, '^\W*\(.\{-}\)\W*$', '\1', '')

    if word == ""
        echohl ErrorMsg | echo "无可用查询内容" | echohl None
        return
    endif

    " 准备系统命令
    let cmd = ""
    if has("win32") || has("win64")
        let cmd = 'start /b c:\Apps\GoldenDict\GoldenDict.exe "' . word . '"'
    elseif has("mac")
        let cmd = 'open -a GoldenDict --args "' . word . '"'
    else
        let cmd = 'goldendict "' . word . '" &'
    endif

    " 执行命令(异步)
    silent! call system(cmd)
    echo "正在查询: " . word
endfunction

确实,这样写更紧凑。deepseek给我写的原始版本在交替查询「光标所在处单词」和「可视模式下选中内容」的时候会出问题,我就把它拆成了俩函数。let word = substitute(word, ‘^\W*(.{-})\W*$’, ‘\1’, ‘’) 这行我用注释符号勾掉,是因为我发现查询中文日文的时候会出问题。如果只需要查英文单词,这行应该是可堪使用的。

深求说最好是写成let word = substitute(word, '^\P{L}*\(.\{-}\)\P{L}*$', '\1', '')
JavaScript 正则表达式可以使用 字符类 尤其是 \w\d 匹配字母或数字,然而,这样的形式只匹配拉丁文字的字符 (换言之,azAZ\w09\d),见示例,这样的使用放到非拉丁文本中是有些蠢的。

Unicode 属性转义 categories 包含更多字符,\p{Letter}\p{Number} 将会适用于任何文字。

问题是,Vim的正则好像没有这unicode扩展,不支持\P{L}……
所以现在只能凑合用

" 定义多语言标点集
let punct = '、。!?「」【】『』・…(){}[],;:!?''"()\[\]{}<>'
let word = substitute(word, '^['.punct.']*\(.\{-}\)['.punct.']*$', '\1', '')

如果想进一步强化的话,可能的思路还包括:

  1. 使用 lemma,如查询 [‘gave’, ‘taken’, ‘looked’, ‘teeth’] 的 lemma 是 [‘give’, ‘take’, ‘look’, ‘tooth’],这对于不支持 lemma 的词典软件比较友好。参考案例
  2. 支持操作符等待命令,更契合 vim 资深用户的使用习惯,参考案例

不过,现有的版本,简明短小,也更容易维护。