Emacs 是有一个完全由 emacs-lisp 实现的 sdcv.el …
我设想的是这样,dict-line可以获取光标附近的单词,这点是goldendict等软件,在emacs这个界面里暂时做不到的,所以,我希望dict-line能把获取到的单词放在系统的剪贴板里,然后goldendict有快捷键可以从剪贴板取词,因此,可以另外用一个程序在检测到剪贴板里是英语单词时,或者dict-line 去触发这样的快捷键,这样就不需要goldendict的命令行接口什么的了。
好吧, 你赢了…
我感觉这样的体验很不好, 也很奇怪
没事,我这人的爱好就是从多个角度琢磨一个问题,并没有抬杠的意思,如有冒犯请多包涵。
哦, 我没那个意思, 怪我没表达清楚
你这个goldendic用法需要启动外部程序goldendic窗口, 启动goldendic后窗口的焦点就不是Emacs了, 所以体验不好
我是用欧路词典和notepad2来尝试,欧路词典的划词翻译的快捷键是 Ctrl+F7,欧路词典启动后,然后我在 notepad2 里输入英语单词,并选中整个单词,然后按 Ctrl+F7 ,出现翻译的浮动框,而这时焦点还在 notepad2 里。所以应该说我不是启动词典,这样会失去焦点,而是使用词典的划词翻译功能。
我试了试,让 kimi 来帮我实现这个代码:
(provide 'call-external-dictionary-mode)
;; 定义一个 minor mode
(define-minor-mode call-external-dictionary-mode
"Toggle Call External Dictionary mode.
With ARG, turn Call External Dictionary mode on if ARG is positive, off otherwise.
Call External Dictionary mode is a minor mode that automatically copies the word at point to the clipboard."
:lighter "ExtDict"
:keymap (let ((map (make-sparse-keymap)))
map)
;; 当模式启用时,添加钩子
(if call-external-dictionary-mode
(add-hook 'post-command-hook 'copy-word-to-clipboard nil t)
;; 当模式禁用时,移除钩子
(remove-hook 'post-command-hook 'copy-word-to-clipboard t)))
;; 定义一个函数,复制当前单词到系统剪贴板
(defun copy-word-to-clipboard ()
"Copy the word at point to the system clipboard."
(interactive)
(let ((word (thing-at-point 'word)))
(when word
;; 根据操作系统执行不同的剪贴板复制操作
(cond
((eq system-type 'windows-nt) ; Windows系统
(w32-set-clipboard-data word 'CF_UNICODETEXT))
((memq system-type '(gnu/linux darwin)) ; Linux或macOS系统
;; 使用xclip命令复制单词到剪贴板
(call-process "xclip" nil nil nil "--selection" "clipboard" nil word)
(message "Word '%s' copied to clipboard." word))))))
;; 提供一个命令来手动启用或禁用 call-external-dictionary-mode
(defun toggle-call-external-dictionary-mode ()
"Toggle call-external-dictionary-mode."
(interactive)
(call-external-dictionary-mode (if call-external-dictionary-mode -1 1)))
然后在配置文件中:
(require 'call-external-dictionary-mode)
(call-external-dictionary-mode 1)
(global-set-key (kbd "C-c w") 'toggle-call-external-dictionary-mode)
可以做到在 windows 下,把鼠标点到哪个单词,就复制到系统剪贴板,然后欧路词典设置剪贴板取词,就出现单词翻译的浮动框。
然而有两个毛病,一是只能在 windows 下起作用,linux 下的的代码虽然有,但是不起作用,而且我已经安装了 xclip 并且测试下来,xclip 生效,在 emacs 中复制单词,欧路也能跳出来,现在就是 emacs 自己不能做到把单词取出来,所以不能复制到系统剪贴板。二是在 windows 下打开 emacs 后,还是要手动切换到让模式生效,才能使用。
你看下你的 Emacs 有没有绑定了 Ctrl + F7 ??
我记得外部软件的全局按键映射优先级比较高的, 比如我的 Emacs 绑定了 ctrl + alt + num5 外部其他软件也绑定这个按键, 在 Emacs 软件内调用这个按键绑定时, 触发的是外部软件的而不是 Emacs 的, 相当与 Emacs 的按键被截止了没有按下这个组合键.
还是说在 Emacs 选中文本后可以使用 Ctrl + F7 按键, 但是没有办法将文本内容复制给系统?
噢噢, 我理解了你的用途了, 也明白了你的困境…
emacs 不给我重新设定 Ctrl+F7 的机会,怎么都是报错 F7 不是一个字符
C-<f7>
默认是为空的, 你应该绑定到别处去了
我建议不要把时间浪费在这些地方上了, 你直接复制内容再打开其他软件用吧, 你想想你浪费的时间都可以进行多少次这样的操作了
就是啊,所以我上面贴的代码就是自动复制然后正好触发欧路的剪贴板取词。
花了点时间,在 linux 和 windows 下都实现了使用外部欧路词典的剪贴板取词功能,并且可以用 C-c C-d 进行切换,linux 下默认是 posframe 优先,windows 下默认是欧路词典优先,因为欧路词典是剪贴板取词,这样会把剪贴板弄乱,所以可以在不需要的时候,切换到 posframe ,代码贴一下,抛砖引玉。
;; -*- coding: utf-8; -*-
;;; dict-line --- View dict in Emacs. -*- lexical-binding: t; -*-
;; Copyright (C) 2024 Free Software Foundation, Inc.
;; License: GPL-3.0-or-later
;; Author: ISouthRain
;; Version: 0.6
;; Package-Requires: ((emacs "24.2") (async "1.8") (posframe "1.0.0"))
;; Keywords: dict sdcv
;; URL: https://github.com/ISouthRain/dict-line
;;; Commentary:
;;
;; This package is quickly view git blame information of the current file line in Emacs in real time.
;;; Code:
(require 'async)
(require 'posframe)
(defgroup dict-line nil
"Emacs dictionary lookup on cursor movement."
:group 'tools)
(defcustom dict-line-dict-directory "~/my-dict/"
"The directory where .ts dictionary files are stored."
:type 'directory
:group 'dict-line)
(defcustom dict-line-dict-personal-file "~/my-dict/my-dict.ts"
"Personal dict file"
:type 'string
:group 'dict-line)
(defcustom dict-line-audio nil
"Toggle play audio file."
:type 'boolean
:group 'dict-line)
(defcustom dict-line-audio-root-dir "~/my-dict/my-audio/"
"The directory where audio files are stored."
:type 'directory
:group 'dict-line)
(defcustom dict-line-audio-play-program "mplayer"
"Play audio file program.
List: `mplayer`, `mpg123`, `mpv`"
:type 'string
:group 'dict-line)
(defcustom dict-line-audio-play-program-arg "-volume 100"
"Audio play program arguments.
Default example: play volume 100%"
:type 'string
:group 'dict-line)
(defcustom dict-line-idle-time 0.5
"Idle time in seconds before triggering dictionary lookup."
:type 'number
:group 'dict-line)
(defcustom dict-line-toggle-display-method-key "C-c C-d"
"Key binding for toggling the display method in `dict-line-mode`."
:type 'string
:group 'dict-line)
(defvar dict-line-word nil
"dict-line point word.")
(defvar dict-line-dict nil
"dict-line result dict txt.")
(defvar dict-line--current-buffer nil
"dict-line word current buffer name.")
(defvar dict-line--posframe-buffer "*dict-line-posframe*"
"dict-line show dict txt buffer.")
(defcustom dict-line-posframe-location #'posframe-poshandler-point-bottom-left-corner
"The location function for displaying the dict-line posframe.
Choose from a list of `posframe` position handlers to control where
the posframe appears relative to the frame, window, or point.
Source for `posframe-show` (2) POSHANDLER:
1. posframe-poshandler-frame-center
2. posframe-poshandler-frame-top-center
3. posframe-poshandler-frame-top-left-corner
4. posframe-poshandler-frame-top-right-corner
5. posframe-poshandler-frame-top-left-or-right-other-corner
6. posframe-poshandler-frame-bottom-center
7. posframe-poshandler-frame-bottom-left-corner
8. posframe-poshandler-frame-bottom-right-corner
9. posframe-poshandler-window-center
10. posframe-poshandler-window-top-center
11. posframe-poshandler-window-top-left-corner
12. posframe-poshandler-window-top-right-corner
13. posframe-poshandler-window-bottom-center
14. posframe-poshandler-window-bottom-left-corner
15. posframe-poshandler-window-bottom-right-corner
16. posframe-poshandler-point-top-left-corner
17. posframe-poshandler-point-bottom-left-corner
18. posframe-poshandler-point-bottom-left-corner-upward
19. posframe-poshandler-point-window-center
20. posframe-poshandler-point-frame-center"
:type '(choice (const nil)
function)
:group 'dict-line)
(defcustom dict-line-display #'dict-line--message
"dict-line to display function."
:type '(choice (const nil)
function)
:group 'dict-line)
(defun dict-line--message ()
"dict-line display function."
(dict-line--dict-convert)
(message dict-line-dict))
(defun dict-line--posframe ()
"Show translation in the posframe"
(dict-line--dict-convert)
(when (posframe-workable-p)
(posframe-show dict-line--posframe-buffer
:string dict-line-dict
:max-width 30
:left-fringe 5
:right-fringe 5
:poshandler dict-line-posframe-location
:border-width 5;; 外边框大小
:border-color "#ed98cc" ;; 边框颜色
)
)
)
(defun dict-line--posframe-delete ()
"Delete the posframe associated with BUFFER if it exists."
(when (eq dict-line-display #'dict-line--posframe)
(posframe-hide dict-line--posframe-buffer))
)
(defun dict-line--dict-convert ()
"dict-line convert dict txt."
(setq dict-line-dict (replace-regexp-in-string "\\\\\\\\n" "\n" dict-line-dict))
(setq dict-line-dict (replace-regexp-in-string "\"," "\" " dict-line-dict))
(setq dict-line-dict (substring dict-line-dict 1 -2))
)
(defun dict-line-toggle-display-method ()
"Toggle between using posframe and clipboard for displaying dictionary results."
(interactive)
(setq dict-line-use-clipboard (not dict-line-use-clipboard))
(message "Dict-line display method toggled: %s" (if dict-line-use-clipboard "clipboard" "posframe")))
(defun dict-line--copy-word-to-clipboard (word)
"Copy WORD to the system clipboard."
;(if (not word)
; (message "No word to copy to clipboard.")
; (message "Copying word to clipboard: %s" word) ; Debug message
(cond
((eq system-type 'windows-nt)
(w32-set-clipboard-data word 'CF_UNICODETEXT))
((memq system-type '(gnu/linux darwin))
; (if (stringp word)
(let ((command (format "echo '%s' | xsel --clipboard --input" word)))
(shell-command command)))))
; (message "Copied to clipboard: %s" word))))
;(t (message "Unsupported operating system.")))))
(defun dict-line--get-dict-async ()
"Check the word under cursor and look it up in the dictionary asynchronously."
(let ((word (if (use-region-p) ;; Check if there is a selected area
(buffer-substring-no-properties (region-beginning) (region-end)) ;; Use selected text
(thing-at-point 'word t))) ;; Otherwise use the word under the cursor
(buffer (get-buffer (buffer-name)))
(dir dict-line-dict-directory)) ;; Extract dictionary directory
(when word
(message "Word at point: %s" word) ;; Debug message
(setq dict-line-word word)
(setq dict-line--current-buffer (get-buffer (buffer-name)))
(if dict-line-use-clipboard
(dict-line--copy-word-to-clipboard word)
(async-start
`(lambda ()
(let ((dict-files (directory-files ,dir t "\\.ts$"))
(dicts nil))
(while (and dict-files (not dicts))
(with-temp-buffer
(insert-file-contents (car dict-files))
(goto-char (point-min))
(when (search-forward (concat "\"" ,word "\":") nil t)
(setq dicts (buffer-substring-no-properties (point) (line-end-position)))))
(setq dict-files (cdr dict-files)))
dicts))
;; Callback
(lambda (dicts)
(when dicts
(setq dict-line-dict dicts)
(with-current-buffer (get-buffer-create dict-line--current-buffer)
(when (functionp dict-line-display)
(funcall dict-line-display)))
)
;; Play audio
(when dict-line-audio
(let* ((first-letter (upcase (substring dict-line-word 0 1))) ;; Get the first letter of the word
(audio-file (concat dict-line-audio-root-dir first-letter "/" dict-line-word ".mp3"))
(program dict-line-audio-play-program)
(args (append (split-string dict-line-audio-play-program-arg) (list audio-file)))) ;; Combine arguments
(when (file-exists-p audio-file)
(let ((process (apply #'start-process "dict-line" nil program args)))
;; Automatically terminate playback after x seconds
(run-at-time "1 sec" nil
(lambda (proc)
(when (process-live-p proc)
(kill-process proc)))
process))))
))
))
))
)
;;;###autoload
(defun dict-line-word-save-from-echo ()
"Extract the word under the cursor, prompt the user to enter information, and then save 'word': 'Input information' to the last line of the specified file."
(interactive)
(let* ((word (thing-at-point 'word t))
(input (read-string (format "Enter information for '%s': " word)))
(entry (format "\"%s\":\"%s\"," word input)))
(when (and word input)
(with-temp-buffer
(insert-file-contents dict-line-dict-personal-file)
(goto-char (point-max))
(insert (concat "\n" entry))
(write-region (point-min) (point-max) dict-line-dict-personal-file))
(message "Save %s to %s" entry dict-line-dict-personal-file))))
;; TODO not completed
;;;###autoload
(define-minor-mode dict-line-mode
"Minor mode to look up words under the cursor asynchronously."
:lighter " DictLine "
:group 'dict-line
:keymap (let ((map (make-sparse-keymap)))
(define-key map (kbd dict-line-toggle-display-method-key)
#'dict-line-toggle-display-method)
map)
(if dict-line-mode
(progn
;; Start the idle timer for asynchronous word lookup
(run-with-idle-timer dict-line-idle-time t #'dict-line--get-dict-async)
;; Add hook to delete posframe after each command
(add-hook 'post-command-hook #'dict-line--posframe-delete))
;; Cancel all timers for dict-line--get-dict-async
(cancel-function-timers #'dict-line--get-dict-async)
;; Remove the hook for deleting posframe
(remove-hook 'post-command-hook #'dict-line--posframe-delete))
)
;;;###autoload
(define-minor-mode global-dict-line-mode
"Toggle Dict Line mode in all buffers."
:global t
:lighter " DictLine"
:keymap (let ((map (make-sparse-keymap)))
(define-key map (kbd dict-line-toggle-display-method-key)
#'dict-line-toggle-display-method)
map)
(if global-dict-line-mode
(dict-line-mode 1)
(dict-line-mode -1)))
;; 在用户配置文件中启用 dict-line-mode
(global-dict-line-mode 1)
(provide 'dict-line)
这是配置文件中的代码:
(require 'dict-line)
(dict-line-mode "🗺️")
;; 全局启用 dict-line-mode
(global-dict-line-mode 1)
(use-package diminish :ensure t)
(setq dict-line-dict-directory (concat slkshareemacs-dir "/dict/dict_data/"))
(setq dict-line-dict-personal-file (concat slkshareemacs-dir "/dict/Mydict.ts"))
(setq dict-line-audio-root-dir (concat slkshareemacs-dir "/dict/pronunciations/"))
(setq dict-line-audio t)
(setq dict-line-display #'dict-line--posframe);; 显示依赖 posframe.el
(setq dict-line-audio-play-program-arg "-volume 65")
(if (eq system-type 'windows-nt)
(setq dict-line-use-clipboard t)
(setq dict-line-use-clipboard nil))
(setq dict-line-toggle-display-method-key "C-c C-d")
linux 上要安装 xsel 。
另外,windows 下的欧路可以直接开启鼠标自动取词,对 emacs 的窗口有效,但是它分割字符串有点困难,它不会按横杠分隔单词。linux 下的欧路不能鼠标自动取词。欧路有浏览器插件,我在想要是能知道它和浏览器之间的接口是咋样的,说不定能在 emacs 中模仿一下。
linux 下的 goldendict-ng ,可以直接在 Emacs 的窗口中取词,只要双击一个单词,emacs 可以选中这个单词,然后 goldendict-ng 就能弹窗翻译,当然要在它的图标上选中翻译弹窗,也可以是剪贴板取词。我觉得都很方便。
顺带一提,一直在用的 sdcv.el 也是很好用的
哈哈, 你用的开心就好, 每个人需求不同, Emacs 就是让大家随心所欲的