├── README.org ├── dict-line.el ├── dict-line.org ├── preview.gif └── preview.png /README.org: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8; -*- 2 | 3 | *大部分源码由 ChatGPT-4 模型生成*, [[file:./dict-line.org][提问过程]] 4 | 5 | * 概述 6 | Emacs 查看字典翻译内容, 并自动播放字典音频文件(可选). 7 | 8 | * Preview 9 | *Messages* 10 | [[./preview.png]] 11 | *Posframe* 12 | [[./preview.gif]] 13 | 14 | * 支持的字典列表 15 | - 下载字典: [[https://github.com/program-in-chinese/vscode_english_chinese_dictionary/tree/master/src/%E8%AF%8D%E5%85%B8%E6%95%B0%E6%8D%AE][vscode_english_chinese_dictionary/src/词典数据 at master · program-in-chinese/vsc...]] 16 | 17 | * 配置示例 18 | #+begin_src emacs-lisp :tangle yes 19 | (use-package dict-line 20 | :diminish 21 | (dict-line-mode "🗺️") 22 | :ensure nil 23 | :load-path "path-to-your/dict-line" 24 | :defer 2 25 | :custom 26 | ;; (dict-line-audio t);; 自动播放音频 27 | ;; (dict-line-audio-root-dir "~/your/pronunciations/");; 音频目录 28 | (dict-line-dict-directory "~/your/dict/");; 字典文件目录 29 | (dict-line-dict-personal-file "~/your/dict/Mydict.ts");; 手动添加字典文件, M-x dict-line-word-save-from-echo 30 | ;; 显示方式 31 | (dict-line-display #'dict-line--posframe);; 显示依赖 posframe.el 32 | ;; 或者使用 message 显示 33 | ;; (dict-line-display #'dict-line--message) 34 | ;; 或者自定义显示方式, 字典内容结果变量: dict-line-word, dict-line-dict 35 | :config 36 | (dict-line-mode t) 37 | ) 38 | #+end_src 39 | 40 | *** 播放单词音频文件 41 | 播放音频外部依赖程序: [[http://www.mplayerhq.hu/design7/dload.html][MPlayer - The Movie Player]] 42 | 43 | 音频文件: [[https://github.com/yousefvand/pronunciations/tree/809cc0ca4d6ac4dbf0519e1caa40e581d41c2df3][GitHub - yousefvand/pronunciations at 809cc0ca4d6ac4dbf0519e1caa40e581d41c2df3]] 44 | #+begin_src emacs-lisp :tangle yes 45 | (setq dict-line-audio-root-dir "path-to/dict/audio") 46 | (setq dict-line-audio t) 47 | #+end_src 48 | 49 | * 参数 50 | - dict-line-dict-directory 51 | 字典文件目录, 必须的 52 | - dict-line-idle-time 53 | 光标空闲多长开始查找单词, 54 | 默认: 0.5 秒 55 | - dict-line-audio 56 | 播放单词音频文件 t/nil 57 | - dict-line-audio-root-dir 58 | 音频文件夹 59 | - dict-line-audio-program 60 | 音频文件播放后端程序, 默认: mplayer 61 | - dict-line-audio-play-program-arg 62 | 音频播放后端程序参数 63 | - dict-line-display 64 | 显示字典内容被调用变量. 65 | - dict-line-posframe-location 66 | 使用 posframe 显示字典内容的位置. 67 | 68 | * TODO [2/5] 69 | - [X] 异步 70 | - [ ] 支持更多 Dict 类型 71 | - [ ] 外部索引, 例如: ripgre, grep 等等 72 | - [X] 支持更多显示方式, 例如: posframe, popup, overlay 73 | - [ ] 支持更多后端, 例如: sdcv 74 | 75 | * 后记 76 | 解析速度蛮不错的, 感觉不到卡顿. 77 | 78 | * 贡献 79 | 欢迎 PR. 80 | 81 | Welcome Pull request. 82 | 83 | * ChangLog 84 | ** V0.6 85 | 新增功能: 支持定义 posframe 显示方式. 86 | ** V0.5 87 | 修复 async.el 引起的错误 buffer 生成 \*emacs:error\* 88 | ** V0.4 89 | 重构, 支持异步. 90 | ** V0.03 91 | 添加 音频 文件 自动播放 支持 92 | ** V0.02 93 | 修复开启 dict-line-mode 导致 modeline 显示 *invalid* 错误 94 | ** V0.01 发布 95 | -------------------------------------------------------------------------------- /dict-line.el: -------------------------------------------------------------------------------- 1 | ;; -*- coding: utf-8; -*- 2 | 3 | ;;; dict-line --- View dict in Emacs. -*- lexical-binding: t; -*- 4 | 5 | ;; Copyright (C) 2024 Free Software Foundation, Inc. 6 | ;; License: GPL-3.0-or-later 7 | 8 | ;; Author: ISouthRain 9 | ;; Version: 0.6 10 | ;; Package-Requires: ((emacs "24.2") (async "1.8") (posframe "1.0.0")) 11 | ;; Keywords: dict sdcv 12 | ;; URL: https://github.com/ISouthRain/dict-line 13 | 14 | ;;; Commentary: 15 | ;; 16 | ;; This package is quickly view git blame information of the current file line in Emacs in real time. 17 | 18 | ;;; Code: 19 | (require 'async) 20 | (require 'posframe) 21 | 22 | (defgroup dict-line nil 23 | "Emacs dictionary lookup on cursor movement." 24 | :group 'tools) 25 | 26 | (defcustom dict-line-dict-directory "~/my-dict/" 27 | "The directory where .ts dictionary files are stored." 28 | :type 'directory 29 | :group 'dict-line) 30 | 31 | (defcustom dict-line-dict-personal-file "~/my-dict/my-dict.ts" 32 | "Personal dict file" 33 | :type 'string 34 | :group 'dict-line) 35 | 36 | (defcustom dict-line-audio nil 37 | "Toggle play audio file." 38 | :type 'boolean 39 | :group 'dict-line) 40 | 41 | (defcustom dict-line-audio-root-dir "~/my-dict/my-audio/" 42 | "The directory where audio files are stored." 43 | :type 'directory 44 | :group 'dict-line) 45 | 46 | (defcustom dict-line-audio-play-program "mplayer" 47 | "Play audio file program. 48 | List: `mplayer`, `mpg123`, `mpv`" 49 | :type 'string 50 | :group 'dict-line) 51 | 52 | (defcustom dict-line-audio-play-program-arg "" 53 | "Audio play program arguments. 54 | Default example: -volume 80 to mplayer play volume 80%" 55 | :type 'string 56 | :group 'dict-line) 57 | 58 | (defcustom dict-line-idle-time 0.5 59 | "Idle time in seconds before triggering dictionary lookup." 60 | :type 'number 61 | :group 'dict-line) 62 | 63 | (defvar dict-line-word nil 64 | "dict-line point word.") 65 | 66 | (defvar dict-line-dict nil 67 | "dict-line result dict txt.") 68 | 69 | (defvar dict-line--current-buffer nil 70 | "dict-line word current buffer name.") 71 | 72 | (defvar dict-line--posframe-buffer "*dict-line-posframe*" 73 | "dict-line show dict txt buffer.") 74 | 75 | (defcustom dict-line-posframe-location #'posframe-poshandler-point-bottom-left-corner 76 | "The location function for displaying the dict-line posframe. 77 | Choose from a list of `posframe` position handlers to control where 78 | the posframe appears relative to the frame, window, or point. 79 | Source for `posframe-show` (2) POSHANDLER: 80 | 1. posframe-poshandler-frame-center 81 | 2. posframe-poshandler-frame-top-center 82 | 3. posframe-poshandler-frame-top-left-corner 83 | 4. posframe-poshandler-frame-top-right-corner 84 | 5. posframe-poshandler-frame-top-left-or-right-other-corner 85 | 6. posframe-poshandler-frame-bottom-center 86 | 7. posframe-poshandler-frame-bottom-left-corner 87 | 8. posframe-poshandler-frame-bottom-right-corner 88 | 9. posframe-poshandler-window-center 89 | 10. posframe-poshandler-window-top-center 90 | 11. posframe-poshandler-window-top-left-corner 91 | 12. posframe-poshandler-window-top-right-corner 92 | 13. posframe-poshandler-window-bottom-center 93 | 14. posframe-poshandler-window-bottom-left-corner 94 | 15. posframe-poshandler-window-bottom-right-corner 95 | 16. posframe-poshandler-point-top-left-corner 96 | 17. posframe-poshandler-point-bottom-left-corner 97 | 18. posframe-poshandler-point-bottom-left-corner-upward 98 | 19. posframe-poshandler-point-window-center 99 | 20. posframe-poshandler-point-frame-center" 100 | :type '(choice (const nil) 101 | function) 102 | :group 'dict-line) 103 | 104 | 105 | (defcustom dict-line-display #'dict-line--message 106 | "dict-line to display function." 107 | :type '(choice (const nil) 108 | function) 109 | :group 'dict-line) 110 | 111 | (defun dict-line--message () 112 | "dict-line display function." 113 | (dict-line--dict-convert) 114 | (message dict-line-dict)) 115 | 116 | (defun dict-line--posframe () 117 | "Show translation in the posframe" 118 | (dict-line--dict-convert) 119 | (when (posframe-workable-p) 120 | (posframe-show dict-line--posframe-buffer 121 | :string dict-line-dict 122 | :max-width 30 123 | :left-fringe 5 124 | :right-fringe 5 125 | :poshandler dict-line-posframe-location 126 | :border-width 5;; 外边框大小 127 | :border-color "#ed98cc" ;; 边框颜色 128 | ) 129 | ) 130 | ) 131 | 132 | (defun dict-line--posframe-delete () 133 | "Delete the posframe associated with BUFFER if it exists." 134 | (when (eq dict-line-display #'dict-line--posframe) 135 | (posframe-hide dict-line--posframe-buffer)) 136 | ) 137 | 138 | (defun dict-line--dict-convert () 139 | "dict-line convert dict txt." 140 | (setq dict-line-dict (replace-regexp-in-string "\\\\\\\\n" "\n" dict-line-dict)) 141 | (setq dict-line-dict (replace-regexp-in-string "\"," "\" " dict-line-dict)) 142 | (setq dict-line-dict (substring dict-line-dict 1 -2)) 143 | ) 144 | 145 | (defun dict-line--get-dict-async () 146 | (interactive) 147 | "Check the word under cursor and look it up in the dictionary asynchronously." 148 | (let ((word (if (use-region-p) ;; Check if there is a selected area 149 | (buffer-substring-no-properties (region-beginning) (region-end)) ;; Use selected text 150 | (thing-at-point 'word t))) ;; Otherwise use the word under the cursor 151 | (buffer (get-buffer (buffer-name))) 152 | (dir dict-line-dict-directory)) ;; Extract dictionary directory 153 | (setq dict-line-word word) 154 | (setq dict-line--current-buffer buffer) ;; Need to query the word buffer 155 | (when (and word (not (minibufferp))) 156 | (async-start 157 | `(lambda () 158 | (let ((dict-files (directory-files ,dir t "\\.ts$")) 159 | (dicts nil)) 160 | (while (and dict-files (not dicts)) 161 | (with-temp-buffer 162 | (insert-file-contents (car dict-files)) 163 | (goto-char (point-min)) 164 | (when (search-forward (concat "\"" ,word "\":") nil t) 165 | (setq dicts (buffer-substring-no-properties (point) (line-end-position))))) 166 | (setq dict-files (cdr dict-files))) 167 | dicts)) 168 | ;; Callback 169 | (lambda (dicts) 170 | (when dicts 171 | (setq dict-line-dict dicts) 172 | (with-current-buffer (get-buffer-create dict-line--current-buffer) 173 | (when (functionp dict-line-display) 174 | (funcall dict-line-display))) 175 | ) 176 | ;; Play audio 177 | (when dict-line-audio 178 | (let* ((first-letter (upcase (substring dict-line-word 0 1))) ;; Get the first letter of the word 179 | (audio-file (concat dict-line-audio-root-dir "/" first-letter "/" (downcase dict-line-word) ".mp3")) 180 | (program dict-line-audio-play-program) 181 | (args (append (split-string dict-line-audio-play-program-arg) (list audio-file)))) ;; Combine arguments 182 | (when (file-exists-p audio-file) 183 | (let ((process (apply #'start-process "dict-line" nil program args))) 184 | ;; Automatically terminate playback after x seconds 185 | (run-at-time "1 sec" nil 186 | (lambda (proc) 187 | (when (process-live-p proc) 188 | (kill-process proc))) 189 | process)))) 190 | ) 191 | )) 192 | )) 193 | ) 194 | 195 | ;;;###autoload 196 | (defun dict-line-word-save-from-echo () 197 | "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." 198 | (interactive) 199 | (let* ((word (thing-at-point 'word t)) 200 | (input (read-string (format "Enter information for '%s': " word))) 201 | (entry (format "\"%s\":\"%s\"," word input))) 202 | (when (and word input) 203 | (with-temp-buffer 204 | (insert-file-contents dict-line-dict-personal-file) 205 | (goto-char (point-max)) 206 | (insert (concat "\n" entry)) 207 | (write-region (point-min) (point-max) dict-line-dict-personal-file)) 208 | (message "Save %s to %s" entry dict-line-dict-personal-file)))) 209 | 210 | ;; TODO not completed 211 | ;;;###autoload 212 | (define-minor-mode dict-line-mode 213 | "Minor mode to look up words under the cursor asynchronously." 214 | :lighter " " 215 | :group 'dict-line 216 | (if dict-line-mode 217 | (progn 218 | ;; Start the idle timer for asynchronous word lookup 219 | (run-with-idle-timer dict-line-idle-time t #'dict-line--get-dict-async) 220 | ;; Add hook to delete posframe after each command 221 | (add-hook 'post-command-hook #'dict-line--posframe-delete)) 222 | ;; Cancel all timers for dict-line--get-dict-async 223 | (cancel-function-timers #'dict-line--get-dict-async) 224 | ;; Remove the hook for deleting posframe 225 | (remove-hook 'post-command-hook #'dict-line--posframe-delete)) 226 | ) 227 | 228 | 229 | (provide 'dict-line) 230 | -------------------------------------------------------------------------------- /dict-line.org: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8; -*- 2 | 3 | * 提问 1: 模仿某个插件 4 | 帮我 模仿 这个 vscode 插件, 链接: https://github.com/program-in-chinese/vscode_english_chinese_dictionary 5 | 在 Emacs modeline 中显示, 提供选项 光标 空闲多长时间, 选择的 dict 目录 6 | 7 | ** 返回结果 1 8 | 这是一个在 Emacs 中实现类似 VSCode 插件功能的基本思路。首先,我们需要一个函数去读取词典文件并返回翻译。然后,我们需要一个 timer 来在光标空闲时运行这个函数。最后,我们将结果显示在 modeline 上。 9 | 10 | 下面是一个简单的实现: 11 | 12 | #+begin_src emacs-lisp :tangle yes 13 | (defvar idle-dict-timer nil 14 | "Timer that is set up when the last command finished.") 15 | 16 | (defvar idle-dict-delay 1 17 | "The number of seconds of idle time to wait before showing translation.") 18 | 19 | (defvar dict-directory "~/dict" 20 | "The directory of dictionary files.") 21 | 22 | (defun idle-dict-cancel-timer () 23 | "Cancel the current idle timer." 24 | (when idle-dict-timer 25 | (cancel-timer idle-dict-timer) 26 | (setq idle-dict-timer nil))) 27 | 28 | (defun idle-dict-show-translation () 29 | "Show translation in the modeline." 30 | (let* ((word (thing-at-point 'word)) 31 | (translation (get-translation word))) 32 | (when translation 33 | (setq-default mode-line-format (list "%e" translation))))) 34 | 35 | (defun get-translation (word) 36 | "Get translation of the WORD from dictionary files." 37 | (let* ((dict-files (directory-files dict-directory t "\\.txt$")) 38 | (translation nil)) 39 | (while (and dict-files (not translation)) 40 | (setq translation (search-dict (car dict-files) word)) 41 | (setq dict-files (cdr dict-files))) 42 | translation)) 43 | 44 | (defun search-dict (dict-file word) 45 | "Search the WORD in the DICT-FILE and return its translation." 46 | (with-temp-buffer 47 | (insert-file-contents dict-file) 48 | (goto-char (point-min)) 49 | (when (search-forward (concat word " ") nil t) 50 | (buffer-substring-no-properties (point) (line-end-position))))) 51 | 52 | (defun idle-dict-set-timer () 53 | "Set a timer to show translation when idle." 54 | (idle-dict-cancel-timer) 55 | (setq idle-dict-timer (run-with-idle-timer idle-dict-delay nil 'idle-dict-show-translation))) 56 | 57 | (add-hook 'post-command-hook 'idle-dict-set-timer) 58 | #+end_src 59 | 60 | 这个代码片段的工作原理是:每次命令完成后,它都会设置一个计时器。当 Emacs 空闲一段时间(由 `idle-dict-delay` 变量控制)后,它会运行 `idle-dict-show-translation` 函数。这个函数会获取光标下的单词,然后从词典文件中查找这个单词的翻译。如果找到翻译,它会将翻译显示在 modeline 上。 61 | 62 | 注意:这个实现非常基础且简单,它可能无法处理所有的边缘情况。例如,它假设词典文件是文本文件,每行包含一个单词和它的翻译,单词和翻译之间用空格分隔。如果你的词典文件的格式不同,你可能需要修改 `get-translation` 和 `search-dict` 函数。 63 | 64 | * 提问 2: 添加 modeline 到最右边, 并保留原有 modeline 内容 65 | 继续优化它, 不要清除 Emacs 原有 modeline 内容, 你应该将它添加到 modeline 最右边 66 | ** 返回结果 2 67 | 我们可以在模式行的右侧添加一个自定义的段落,用于显示翻译。我们可以通过定义一个全局变量来存储翻译,并将这个变量添加到 `mode-line-format` 中。当我们获取到新的翻译时,我们只需要更新这个变量,模式行就会自动更新。 68 | 69 | 下面是如何做到这一点的代码: 70 | 71 | #+begin_src emacs-lisp :tangle yes 72 | (defvar idle-dict-timer nil 73 | "Timer that is set up when the last command finished.") 74 | 75 | (defvar idle-dict-delay 1 76 | "The number of seconds of idle time to wait before showing translation.") 77 | 78 | (defvar dict-directory "~/dict" 79 | "The directory of dictionary files.") 80 | 81 | (defvar idle-dict-translation nil 82 | "The translation to be shown in the modeline.") 83 | 84 | (defun idle-dict-cancel-timer () 85 | "Cancel the current idle timer." 86 | (when idle-dict-timer 87 | (cancel-timer idle-dict-timer) 88 | (setq idle-dict-timer nil))) 89 | 90 | (defun idle-dict-show-translation () 91 | "Show translation in the modeline." 92 | (let* ((word (thing-at-point 'word)) 93 | (translation (get-translation word))) 94 | (when translation 95 | (setq idle-dict-translation translation) 96 | (force-mode-line-update)))) 97 | 98 | (defun get-translation (word) 99 | "Get translation of the WORD from dictionary files." 100 | (let* ((dict-files (directory-files dict-directory t "\\.txt$")) 101 | (translation nil)) 102 | (while (and dict-files (not translation)) 103 | (setq translation (search-dict (car dict-files) word)) 104 | (setq dict-files (cdr dict-files))) 105 | translation)) 106 | 107 | (defun search-dict (dict-file word) 108 | "Search the WORD in the DICT-FILE and return its translation." 109 | (with-temp-buffer 110 | (insert-file-contents dict-file) 111 | (goto-char (point-min)) 112 | (when (search-forward (concat word " ") nil t) 113 | (buffer-substring-no-properties (point) (line-end-position))))) 114 | 115 | (defun idle-dict-set-timer () 116 | "Set a timer to show translation when idle." 117 | (idle-dict-cancel-timer) 118 | (setq idle-dict-timer (run-with-idle-timer idle-dict-delay nil 'idle-dict-show-translation))) 119 | 120 | (add-hook 'post-command-hook 'idle-dict-set-timer) 121 | 122 | (setq-default mode-line-format (list "%e" 'mode-line-front-space 'mode-line-mule-info 'mode-line-client 'mode-line-modified 'mode-line-remote 'mode-line-frame-identification 'mode-line-buffer-identification " " 'mode-line-position '(vc-mode vc-mode) " " 'mode-line-modes '(which-func-mode ("" which-func-format "--")) '(global-mode-string ("--" global-mode-string)) '(-3 . "%p") '(" " idle-dict-translation))) 123 | 124 | #+end_src 125 | 126 | 在这个版本的代码中,我们定义了一个新的变量 `idle-dict-translation` 来存储翻译。我们在 `idle-dict-show-translation` 函数中更新这个变量,然后调用 `force-mode-line-update` 来强制更新模式行。我们还修改了 `mode-line-format`,在其末尾添加了 `idle-dict-translation`。这样,每次 `idle-dict-translation` 更新时,模式行就会自动显示新的翻译。 127 | 128 | 注意:这个实现仍然非常基础且简单,它可能无法处理所有的边缘情况。例如,它假设词典文件是文本文件,每行包含一个单词和它的翻译,单词和翻译之间用空格分隔。如果你的词典文件的格式不同,你可能需要修改 `get-translation` 和 `search-dict` 函数。 129 | 130 | * 提问 3: 完整匹配 131 | 继续优化它, 文件内容一般如下: 132 | "asam": "n. (Asam)人名;(阿拉伯、土、德)阿萨姆", 133 | "Asama": "浅间山(日本本州中部的一座火山)", 134 | "asami": "n. (Asami)人名;(日)朝美(名)" 135 | 136 | 比如 Emacs 当前光标下的 word 为 asami , 你应该搜索 "asami" 而不是仅仅搜索 asami 137 | 如果搜索到了 "asami" , 那么 modeline 显示 n. (Asami)人名;(日)朝美(名) 138 | 139 | ** 返回结果 3 140 | 根据你提供的词典文件的格式,我们可以稍微修改一下 `search-dict` 函数,使其在单词前后添加双引号进行搜索。同时,我们也需要在找到匹配项后,从返回的字符串中删除单词本身和前后的双引号。 141 | 142 | 下面是修改后的代码: 143 | 144 | 145 | #+begin_src emacs-lisp :tangle yes 146 | (defvar idle-dict-timer nil 147 | "Timer that is set up when the last command finished.") 148 | 149 | (defvar idle-dict-delay 1 150 | "The number of seconds of idle time to wait before showing translation.") 151 | 152 | (defvar dict-directory "~/dict" 153 | "The directory of dictionary files.") 154 | 155 | (defvar idle-dict-translation nil 156 | "The translation to be shown in the modeline.") 157 | 158 | (defun idle-dict-cancel-timer () 159 | "Cancel the current idle timer." 160 | (when idle-dict-timer 161 | (cancel-timer idle-dict-timer) 162 | (setq idle-dict-timer nil))) 163 | 164 | (defun idle-dict-show-translation () 165 | "Show translation in the modeline." 166 | (let* ((word (thing-at-point 'word)) 167 | (translation (get-translation word))) 168 | (when translation 169 | (setq idle-dict-translation translation) 170 | (force-mode-line-update)))) 171 | 172 | (defun get-translation (word) 173 | "Get translation of the WORD from dictionary files." 174 | (let* ((dict-files (directory-files dict-directory t "\\.txt$")) 175 | (translation nil)) 176 | (while (and dict-files (not translation)) 177 | (setq translation (search-dict (car dict-files) word)) 178 | (setq dict-files (cdr dict-files))) 179 | translation)) 180 | 181 | (defun search-dict (dict-file word) 182 | "Search the WORD in the DICT-FILE and return its translation." 183 | (with-temp-buffer 184 | (insert-file-contents dict-file) 185 | (goto-char (point-min)) 186 | (when (search-forward (concat "\"" word "\"") nil t) 187 | (buffer-substring-no-properties (point) (line-end-position))))) 188 | 189 | (defun idle-dict-set-timer () 190 | "Set a timer to show translation when idle." 191 | (idle-dict-cancel-timer) 192 | (setq idle-dict-timer (run-with-idle-timer idle-dict-delay nil 'idle-dict-show-translation))) 193 | 194 | (add-hook 'post-command-hook 'idle-dict-set-timer) 195 | 196 | (setq-default mode-line-format (list "%e" 'mode-line-front-space 'mode-line-mule-info 'mode-line-client 'mode-line-modified 'mode-line-remote 'mode-line-frame-identification 'mode-line-buffer-identification " " 'mode-line-position '(vc-mode vc-mode) " " 'mode-line-modes '(which-func-mode ("" which-func-format "--")) '(global-mode-string ("--" global-mode-string)) '(-3 . "%p") '(" " idle-dict-translation))) 197 | 198 | #+end_src 199 | 200 | 这个版本的代码应该能更准确地从你的词典文件中找到翻译。请注意,这个代码仍然假设词典文件中的每一行都包含一个单词和它的翻译,单词和翻译之间用空格分隔。如果你的词典文件的格式不同,你可能需要进一步修改 `get-translation` 和 `search-dict` 函数。 201 | 202 | * 提问 4: 做成 global-mode 203 | 做的非常好, 现在把他做成 Emacs 包, 包名前缀为 dict-line 204 | 新增 global-mode 或 majo-mode 开启使用 205 | 206 | ** 返回结果 4, 完全可用 207 | 好的,我们可以将这个功能打包成一个 Emacs 包。下面是一个例子,包括了全局模式 `global-dict-line-mode` 和主模式 `dict-line-mode`。 208 | 209 | #+begin_src emacs-lisp :tangle yes 210 | (defgroup dict-line nil 211 | "Show word translation in mode line." 212 | :group 'convenience) 213 | 214 | (defcustom dict-line-dict-directory "~/dict" 215 | "The directory of dictionary files." 216 | :type 'directory 217 | :group 'dict-line) 218 | 219 | (defcustom dict-line-idle-delay 1 220 | "The number of seconds of idle time to wait before showing translation." 221 | :type 'number 222 | :group 'dict-line) 223 | 224 | (defvar dict-line--timer nil 225 | "Timer that is set up when the last command finished.") 226 | 227 | (defvar dict-line--translation nil 228 | "The translation to be shown in the modeline.") 229 | 230 | (defun dict-line-cancel-timer () 231 | "Cancel the current idle timer." 232 | (when dict-line--timer 233 | (cancel-timer dict-line--timer) 234 | (setq dict-line--timer nil))) 235 | 236 | (defun dict-line-show-translation () 237 | "Show translation in the modeline." 238 | (let* ((word (thing-at-point 'word t)) 239 | (translation (when word (dict-line-get-translation word)))) 240 | (when translation 241 | (setq dict-line--translation translation) 242 | (force-mode-line-update)))) 243 | 244 | (defun dict-line-get-translation (word) 245 | "Get translation of the WORD from dictionary files." 246 | (let* ((dict-files (directory-files dict-line-dict-directory t "\\.txt$")) 247 | (translation nil)) 248 | (while (and dict-files (not translation)) 249 | (setq translation (dict-line-search-dict (car dict-files) word)) 250 | (setq dict-files (cdr dict-files))) 251 | translation)) 252 | 253 | (defun dict-line-search-dict (dict-file word) 254 | "Search the WORD in the DICT-FILE and return its translation." 255 | (with-temp-buffer 256 | (insert-file-contents dict-file) 257 | (goto-char (point-min)) 258 | (when (search-forward (concat "\"" word "\":") nil t) 259 | (buffer-substring-no-properties (point) (line-end-position))))) 260 | 261 | (defun dict-line-set-timer () 262 | "Set a timer to show translation when idle." 263 | (dict-line-cancel-timer) 264 | (setq dict-line--timer (run-with-idle-timer dict-line-idle-delay nil 'dict-line-show-translation))) 265 | 266 | (define-minor-mode dict-line-mode 267 | "Minor mode to show word translation in mode line." 268 | :lighter " DictLine" 269 | :global nil 270 | (if dict-line-mode 271 | (progn 272 | (add-hook 'post-command-hook 'dict-line-set-timer nil t) 273 | (unless (memq 'dict-line--translation global-mode-string) 274 | (setq global-mode-string (append global-mode-string '(dict-line--translation))))) 275 | (remove-hook 'post-command-hook 'dict-line-set-timer t) 276 | (setq global-mode-string (remq 'dict-line--translation global-mode-string)))) 277 | 278 | (define-globalized-minor-mode global-dict-line-mode 279 | dict-line-mode 280 | (lambda () (dict-line-mode 1))) 281 | 282 | (provide 'dict-line) 283 | #+end_src 284 | 285 | 这个包定义了两个模式:`dict-line-mode` 和 `global-dict-line-mode`。`dict-line-mode` 是一个主模式,只在当前缓冲区中启用。`global-dict-line-mode` 是一个全局模式,它在所有缓冲区中启用 `dict-line-mode`。 286 | 287 | 要使用这个包,你需要将这个代码保存为一个文件,比如 `dict-line.el`,然后在你的 Emacs 配置文件中添加以下代码: 288 | 289 | #+begin_src emacs-lisp :tangle yes 290 | (add-to-list 'load-path "/path/to/dict-line.el") 291 | (require 'dict-line) 292 | (global-dict-line-mode 1) 293 | #+end_src 294 | 295 | 请将 `"/path/to/dict-line.el"` 替换为 `dict-line.el` 文件的实际路径。这个代码将启用 `global-dict-line-mode`,这样你就可以在模式行中看到翻译了。 296 | 297 | 注意:这个实现仍然非常基础且简单,它可能无法处理所有的边缘情况。例如,它假设词典文件是文本文件,每行包含一个单词和它的翻译,单词和翻译之间用空格分隔。如果你的词典文件的格式不同,你可能需要修改 `dict-line-get-translation` 和 `dict-line-search-dict` 函数。 298 | 299 | * 总结 300 | 已经待到可用状态了, 速度也快,剩下的是个人 优化 301 | -------------------------------------------------------------------------------- /preview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ISouthRain/dict-line/a6eedd41ffdb3c99ed8b80023fb0a21b31848b80/preview.gif -------------------------------------------------------------------------------- /preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ISouthRain/dict-line/a6eedd41ffdb3c99ed8b80023fb0a21b31848b80/preview.png --------------------------------------------------------------------------------