add macOS Easydict.app support
[external-dict.el.git] / external-dict.el
blobc2ed72b08fc1674837675c03f61b9e5cecfbffcd
1 ;;; external-dict.el --- Query external dictionary like goldendict, Bob.app etc
3 ;; Authors: stardiviner <numbchild@gmail.com>
4 ;; Package-Requires: ((emacs "25.1"))
5 ;; Package-Version: 0.1
6 ;; Keywords: wp processes
7 ;; homepage: https://repo.or.cz/external-dict.el.git
8 ;; SPDX-License-Identifier: GPL-2.0-or-later
10 ;; You should have received a copy of the GNU General Public License
11 ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
13 ;;; Commentary:
15 ;; Usage:
17 ;; (global-set-key (kbd "C-x d") 'external-dict-dwim)
18 ;; If invoke with [C-u] prefix, then it will raise the main window.
20 ;;; Code:
22 (declare-function ns-do-applescript "nsfns.m" t)
24 (defgroup external-dict nil
25 "Use external dictionary in Emacs."
26 :prefix "external-dict-"
27 :group 'dictionary)
29 (defcustom external-dict-cmd
30 (cl-case system-type
31 (gnu/linux
32 (cond
33 ((executable-find "goldendict")
34 '(:dict-program "goldendict" :command-p t))))
35 (darwin
36 (cond
37 ((file-exists-p "/Applications/Easydict.app")
38 '(:dict-program "Easydict.app" :command-p nil))
39 ((file-exists-p "/Applications/Bob.app")
40 '(:dict-program "Bob.app" :command-p nil))
41 ((file-exists-p "/Applications/GoldenDict.app")
42 '(:dict-program "GoldenDict.app" :command-p t))
43 (t '(:dict-program "Dictionary.app" :command-p t)))))
44 "Specify external dictionary command."
45 :type 'string
46 :group 'external-dict)
48 (defcustom external-dict-read-cmd
49 (cl-case system-type
50 (gnu/linux
51 (cl-case (plist-get external-dict-cmd :dict-program)
52 ("goldendict" nil)
54 (cond
55 ((executable-find "festival") "festival")
56 ((executable-find "espeak") "espeak")))))
57 (darwin
58 (pcase (plist-get external-dict-cmd :dict-program)
59 ("Bob.app" "say")
60 ("GoldenDict.app" "say")
61 ("Dictionary.app" "say"))))
62 "Specify external tool command to read the query word.
63 If the value is nil, it will let dictionary handle it without invoke the command.
64 If the value is a command string, it will invoke the command to read the word."
65 :type 'string
66 :safe #'stringp)
68 (defun external-dict--get-text ()
69 "Get word or text from region selected, thing-at-point, or interactive input."
70 (cond
71 ((region-active-p)
72 (let ((text (buffer-substring-no-properties (mark) (point))))
73 (deactivate-mark)
74 `(:type :text :text ,text)))
75 ((and (thing-at-point 'word)
76 (not (string-blank-p (substring-no-properties (thing-at-point 'word)))))
77 (let ((word (substring-no-properties (thing-at-point 'word))))
78 `(:type :word, :text ,word)))
79 (t (let ((word (read-string "[external-dict.el] Query word: ")))
80 `(:type :word :text ,word)))))
82 ;;;###autoload
83 (defun external-dict-read-word (word)
84 "Auto pronounce the query word or read the text."
85 (interactive)
86 (sit-for 1)
87 (pcase external-dict-read-cmd
88 ("say"
89 (shell-command (concat "say " (shell-quote-argument word))))
90 ("festival"
91 (shell-command (concat "festival --tts " (shell-quote-argument word))))
92 ("espeak"
93 (shell-command (concat "espeak " (shell-quote-argument word))))))
95 ;;; [ macOS Dictionary.app ]
96 ;;;###autoload
97 (defun external-dict-Dictionary.app (word)
98 "Query TEXT like current symbol/world at point or region selected or input with macOS Dictionary.app."
99 (interactive
100 (list (cond
101 ((region-active-p)
102 (buffer-substring-no-properties (mark) (point)))
103 ((not (string-blank-p (substring-no-properties (thing-at-point 'word))))
104 (substring-no-properties (thing-at-point 'word)))
105 (t (read-string "[external-dict.el] Query word in macOS Dictionary.app: ")))))
106 (deactivate-mark)
107 (shell-command (format "open dict://\"%s\"" word))
108 (external-dict-read-word word))
110 ;;; [ Goldendict ]
111 (defun external-dict-goldendict--ensure-running ()
112 "Ensure goldendict program is running."
113 (unless (string-match "goldendict" (shell-command-to-string "ps -C 'goldendict' | sed -n '2p'"))
114 (start-process-shell-command
115 "*goldendict*"
116 " *goldendict*"
117 "goldendict")))
119 ;;;###autoload
120 (defun external-dict-goldendict (word)
121 "Query current symbol/word at point or region selected with goldendict.
122 If you invoke command with `RAISE-MAIN-WINDOW' prefix \\<universal-argument>,
123 it will raise external dictionary main window."
124 (interactive (list (plist-get (external-dict--get-text) :text)))
125 (external-dict-goldendict--ensure-running)
126 (let ((goldendict-cmd (cl-case system-type
127 (gnu/linux (executable-find "goldendict"))
128 (darwin (or (executable-find "GoldenDict") (executable-find "goldendict")))
129 (t (plist-get external-dict-cmd :dict-program)))))
130 (if current-prefix-arg
131 (save-excursion
132 (call-process goldendict-cmd nil nil nil))
133 (save-excursion
134 ;; pass the selection to shell command goldendict.
135 ;; use Goldendict API: "Scan Popup"
136 (call-process goldendict-cmd nil nil nil word))
137 (external-dict-read-word word)
138 (deactivate-mark))))
140 ;;; alias for `external-dict-cmd' property `:dict-program' name under macOS.
141 (defalias 'external-dict-GoldenDict.app 'external-dict-goldendict)
143 ;;; [ Bob.app ]
144 ;;;###autoload
145 (defun external-dict-Bob.app-translate (text)
146 "Bob.app translate TEXT."
147 (let ((path "translate")
148 (action "translateText")
149 (text text))
150 (ns-do-applescript
151 (format "use scripting additions
152 use framework \"Foundation\"
153 on toJson(recordValue)
154 (((current application's NSString)'s alloc)'s initWithData:((current application's NSJSONSerialization)'s dataWithJSONObject:recordValue options:1 |error|:(missing value)) encoding:4) as string
155 end toJson
157 set theRecord to {|path|: \"%s\", body: {action: \"%s\", |text|: \"%s\", windowLocation: \"center\", inputBoxState: \"alwaysUnfold\"}}
158 set theParameter to toJson(theRecord)
159 tell application id \"com.hezongyidev.Bob\" to request theParameter
161 path action text))))
163 (defun external-dict-Bob.app-dictionary (word)
164 "macOS Bob.app query dictionary for WORD."
165 (ns-do-applescript
166 (format
167 "tell application \"Bob\"
168 launch
169 translate \"%s\"
170 end tell" word))
171 (external-dict-read-word word))
173 (defun external-dict-Bob.app ()
174 "Translate text with Bob.app on macOS."
175 (interactive)
176 (let* ((return-plist (external-dict--get-text))
177 (type (plist-get return-plist :type))
178 (text (plist-get return-plist :text)))
179 (cond
180 ((eq type :word)
181 (external-dict-Bob.app-dictionary text))
182 ((eq type :text)
183 (external-dict-Bob.app-translate text)))))
185 (defun external-dict-Easydict.app ()
186 "Translate text with Easydict.app on macOS.
187 Easydict.app URL scheme easydict://query?text=good%20girl
188 You can open the URL scheme with shell command:
189 $ open \"easydict://query?text=good%20girl\"
191 (interactive)
192 (let* ((return-plist (external-dict--get-text))
193 (type (plist-get return-plist :type))
194 (text (plist-get return-plist :text)))
195 (make-process
196 :name "external-dict-Easydict.app"
197 :command (list "open" (format "easydict://query?text=%s" (url-encode-url text))))
198 ;; (external-dict-read-word word)
201 ;;;###autoload
202 (defun external-dict-dwim ()
203 "Query current symbol/word at point or region selected with external dictionary."
204 (interactive)
205 (let ((dict-program (plist-get external-dict-cmd :dict-program)))
206 (call-interactively (intern (format "external-dict-%s" dict-program)))))
210 (provide 'external-dict)
212 ;;; external-dict.el ends here