├── README.md ├── opencc-tests.el └── opencc.el /README.md: -------------------------------------------------------------------------------- 1 | # opencc.el [![MELPA](https://melpa.org/packages/opencc-badge.svg)](https://melpa.org/#/opencc) 2 | 3 | ## 介绍 4 | 5 | `opencc.el` 利用 [OpenCC](https://github.com/BYVoid/OpenCC) 的命令行工具实现在 Emacs 内进行中文简繁转换。 6 | 7 | ## 需求 8 | 9 | - Emacs 版本至少 24.4 10 | - OpenCC 11 | 12 | ## 使用 13 | 14 | ### `(opencc-string CONFIG STRING)` 15 | 16 | 按配置文件 CONFIG 转换字符串 STRING。比如把简体转换成繁体: 17 | 18 | 19 | ```el 20 | (opencc-string "s2t" "简繁转换") 21 | => "簡繁轉換" 22 | ``` 23 | 24 | ### `(opencc-region CONFIG START END)` 25 | 26 | 按配置文件 CONFIG 转换 START 和 END 之间的文字。 27 | 28 | ### `M-x opencc-message` 29 | 30 | 从 minibuffer 读取配置和字符串,然后用 `message` 显示结果。使用举例: 31 | 32 | M-x opencc-message s2t RET 简繁转换 RET 33 | 34 | 会显示“簡繁轉換”。 35 | 36 | ### `M-x opencc-replace-at-point` 37 | 38 | 从 Minibuffer 读取配置,转换并替换选中区域内的中文。如果没有可用的选中区域,用光标下的一句话。 39 | 40 | ### `M-x opencc-print-buffer` 41 | 42 | 转化当前 Buffer 的内容,在另一个 Buffer 中显示转换的结果。 43 | 44 | ### `M-x opencc-insert-mode` 45 | 46 | 47 | 输入的简体自动转换层繁体。可通过选项 `opencc-insert-mode-config` 调整转换的方向。 48 | 49 | ### `M-x opencc-isearch-mode` 50 | 51 | 输入简体搜索对应的繁体。可通过 `opencc-isearch-mode-config` 调整转换的方向。 52 | 53 | ## 备注 54 | 55 | ### [OpenCC 预设配置文件](https://github.com/BYVoid/OpenCC#configurations-配置文件) 56 | 57 | | 配置 | 说明 | 说明 | 58 | | --- | --- | --- | 59 | | s2t | 简体到繁体 | Simplified Chinese to Traditional Chinese | 60 | | t2s | 繁体到简体 | Traditional Chinese to Simplified Chinese | 61 | | s2tw | 简体到台湾正体 | Simplified Chinese to Traditional Chinese | 62 | | tw2s | 台湾正体到简体 | Traditional Chinese to Simplified Chinese | 63 | | s2hk | 简体到香港繁体(香港小学学习字词表标准) | Simplified Chinese to Traditional Chinese | 64 | | hk2s | 香港繁体(香港小学学习字词表标准)到简体 | Traditional Chinese to Simplified Chinese | 65 | | s2twp | 简体到繁体(台湾正体标准)并转换为台湾常用词汇 | Simplified Chinese to Traditional Chinese with Taiwanese idiom | 66 | | tw2sp | 繁体(台湾正体标准)到简体并转换为中国大陆常用词汇 | Traditional Chinese to Simplified Chinese with Mainland Chinese idiom | 67 | -------------------------------------------------------------------------------- /opencc-tests.el: -------------------------------------------------------------------------------- 1 | ;;; opencc-tests.el --- Tests for opencc.el -*- lexical-binding: t; -*- 2 | 3 | ;;; Code: 4 | 5 | (require 'opencc) 6 | 7 | (unless (string= (opencc-string "s2t" "让我们团结起来") 8 | "讓我們團結起來") 9 | (error "opencc-string")) 10 | 11 | ;; ert 12 | ;; benchmark 13 | 14 | ;;; opencc-tests.el ends here 15 | -------------------------------------------------------------------------------- /opencc.el: -------------------------------------------------------------------------------- 1 | ;;; opencc.el --- 中文简繁转换 <-> 中文簡繁轉換 (Convert Chinese with OpenCC) -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2017 徐春阳 4 | 5 | ;; Author: 徐春阳 6 | ;; URL: https://github.com/xuchunyang/emacs-opencc 7 | ;; Version: 0 8 | ;; Package-Requires: ((emacs "24.4")) 9 | ;; Keywords: Chinese 10 | ;; Created: 公历2017年6月14日,星期三 11 | 12 | ;; This program is free software; you can redistribute it and/or modify 13 | ;; it under the terms of the GNU General Public License as published by 14 | ;; the Free Software Foundation, either version 3 of the License, or 15 | ;; (at your option) any later version. 16 | 17 | ;; This program is distributed in the hope that it will be useful, 18 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | ;; GNU General Public License for more details. 21 | 22 | ;; You should have received a copy of the GNU General Public License 23 | ;; along with this program. If not, see . 24 | 25 | ;;; Commentary: 26 | 27 | ;; `opencc.el' is a package for conversion between Traditional and 28 | ;; Simplified Chinese in Emacs using OpenCC's command line tool. 29 | ;; 30 | ;; [OpenCC] https://github.com/BYVoid/OpenCC 31 | 32 | ;;; Code: 33 | 34 | ;;; Options 35 | 36 | (defgroup opencc nil 37 | "中文简繁转换 <-> 中文簡繁轉換." 38 | :group 'external) 39 | 40 | (defcustom opencc-command "opencc" 41 | "OpenCC 命令行工具." 42 | :group 'opencc 43 | :type '(string)) 44 | 45 | (defcustom opencc-sleep 0.1 46 | "OpenCC 命令行工具完成初始化所需时间的上限(单位是秒). 47 | 48 | 为了提高速度,opencc 以异步子进程(Asynchronous Process)的方式运行, 49 | 但它启动完成时没有任何输出,因此 Emacs 不能知道它是还在启动中、还 50 | 是已经结束启动了. 所以不得不 Emacs 暂停一会,确保 opencc 能完成启动. 51 | 52 | 它的默认值时 0.1 秒,是因为在我的电脑上,opencc 的启动时间是明显少于 0.1 秒的: 53 | 54 | ~$ time opencc -c s2t <<< 汉字 55 | 漢字 56 | 57 | real 0m0.057s 58 | user 0m0.037s 59 | sys 0m0.007s" 60 | :group 'opencc 61 | :type '(number)) 62 | 63 | (defcustom opencc-configuration-files '("s2t" 64 | "t2s" 65 | "s2tw" 66 | "tw2s" 67 | "s2hk" 68 | "hk2s" 69 | "s2twp" 70 | "tw2sp") 71 | "OpenCC 命令行工具的配置文件. 72 | 73 | 默认值是 OpenCC 预装的配置,说明如下: 74 | 75 | | 配置文件 | 说明 | | 76 | |----------+----------------------------------------------------+-----------------------------------------------------------------------------------------| 77 | | s2t | 简体到繁体 | Simplified Chinese to Traditional Chinese | 78 | | t2s | 繁体到简体 | Traditional Chinese to Simplified Chinese | 79 | | s2tw | 简体到台湾正体 | Simplified Chinese to Traditional Chinese | 80 | | tw2s | 台湾正体到简体 | Traditional Chinese to Simplified Chinese | 81 | | s2hk | 简体到香港繁体(香港小学学习字词表标准) | Simplified Chinese to Traditional Chinese | 82 | | hk2s | 香港繁体(香港小学学习字词表标准)到简体 | Traditional Chinese to Simplified Chinese | 83 | | s2twp | 简体到繁体(台湾正体标准)并转换为台湾常用词汇 | Simplified Chinese to Traditional Chinese with Taiwanese idiom | 84 | | tw2sp | 繁体(台湾正体标准)到简体并转换为中国大陆常用词汇 | Traditional Chinese to Simplified Chinese with Mainland Chinese idiom | 85 | " 86 | :group 'opencc 87 | :type '(repeat (string :tag "配置文件"))) 88 | 89 | (defcustom opencc-insert-mode-config "s2t" 90 | "`opencc-insert-mode' 使用的配置文件." 91 | :group 'opencc 92 | :type '(string :tag "配置文件")) 93 | 94 | (defcustom opencc-insert-mode-lighter " OpenCC-Insert" 95 | "`opencc-insert-mode' 在 Mode Line 上的提示符." 96 | :group 'opencc 97 | :type '(choice (const :tag "none" nil) 98 | string)) 99 | 100 | (defcustom opencc-isearch-mode-config "s2t" 101 | "`opencc-isearch-mode' 使用的配置文件." 102 | :group 'opencc 103 | :type '(string :tag "配置文件")) 104 | 105 | (defcustom opencc-isearch-mode-lighter " OpenCC-Isearch" 106 | "`opencc-isearch-mode' 在 Mode Line 上的提示符." 107 | :group 'opencc 108 | :type '(choice (const :tag "none" nil) 109 | string)) 110 | 111 | ;;; Internal helpers 112 | 113 | (defmacro opencc-aif (test-form then-form &rest else-forms) 114 | "Anaphoric version of `if'. 115 | Like `if' but set the result of TEST-FORM in a temporary variable called `it'. 116 | THEN-FORM and ELSE-FORMS are then excuted just like in `if'." 117 | (declare (indent 2) (debug t)) 118 | `(let ((it ,test-form)) 119 | (if it ,then-form ,@else-forms))) 120 | 121 | (defmacro opencc-awhen (test &rest body) 122 | "Anaphoric version of `when'." 123 | (declare (indent 1)) 124 | `(let ((it ,test)) 125 | (when it ,@body))) 126 | 127 | ;;; API 128 | 129 | ;;;###autoload 130 | (defun opencc-string (config string) 131 | "按配置文件 CONFIG 转换字符串 STRING. 132 | 133 | 如果你没有自己的配置文件,请到在 `opencc-configuration-files' 中选择一个." 134 | (let* ((proc-name (format " *opencc-%s*" config)) 135 | (proc-buffer proc-name) 136 | (proc (and (get-buffer proc-buffer) 137 | (get-buffer-process proc-buffer))) 138 | result) 139 | (unless proc 140 | (setq proc 141 | (start-process proc-name 142 | proc-buffer 143 | opencc-command 144 | "--config" config)) 145 | (set-process-query-on-exit-flag proc nil) 146 | ;; XXX `opencc' 启动完成时,也不会有任何输出,所以没办法知道它是 147 | ;; 否已经准备好接收输入了,可以 148 | ;; 1. 确定下 Emacs 是不是就不能处理这种情况; 149 | ;; 2. 向上游 OpenCC 寻求帮助; 150 | ;; 3. 自己写一个 OpenCC 的 Wrapper 151 | (sleep-for opencc-sleep)) 152 | (with-current-buffer proc-buffer 153 | (unless (eq (process-status proc) 'run) 154 | (message "%s" (buffer-string)) 155 | (delete-region (point-min) (point-max)) 156 | (error "Process %s is not running" proc)) 157 | (delete-region (point-min) (point-max)) 158 | (process-send-string proc (concat string "\n")) 159 | (accept-process-output proc) 160 | (setq result (buffer-substring (point-min) (1- (point-max))))) 161 | result)) 162 | 163 | ;;;###autoload 164 | (defun opencc-region (config start end) 165 | "按配置文件 CONFIG 转换 START 和 END 之间的文字. 166 | 167 | 如果你没有自己的配置文件,请到在 `opencc-configuration-files' 中选择一个." 168 | (opencc-string config (buffer-substring-no-properties start end))) 169 | 170 | ;;; User commands 171 | 172 | ;;;###autoload 173 | (defun opencc-message () 174 | "一个交互命令,使用 minibuffer 和 echo area 读取输入和显示结果." 175 | (interactive) 176 | (let ((config (completing-read 177 | "配置文件: " 178 | opencc-configuration-files)) 179 | (string (if (use-region-p) 180 | (buffer-substring (region-beginning) (region-end)) 181 | (read-string "需转化文字:" nil nil (thing-at-point 'word))))) 182 | (message "%s" (opencc-string config string)))) 183 | (put 'opencc-message 'interactive-only 'opencc-string) 184 | 185 | ;;;###autoload 186 | (defun opencc-replace-at-point () 187 | "一个交互命令,转化并替换光标下的文字." 188 | (interactive "*") 189 | (let* ((config (completing-read 190 | "配置文件: " 191 | opencc-configuration-files)) 192 | start end 193 | (string (if (use-region-p) 194 | (progn (setq start (region-beginning) 195 | end (region-end)) 196 | (buffer-substring start end)) 197 | (opencc-awhen (bounds-of-thing-at-point 'word) 198 | (setq start (car it) 199 | end (cdr it)) 200 | (buffer-substring start end))))) 201 | (unless string 202 | (apply (if (fboundp 'user-error) 203 | #'user-error 204 | #'error) 205 | '("Nothing at point to replace"))) 206 | (opencc-awhen (opencc-region config start end) 207 | (delete-region start end) 208 | (insert it)))) 209 | (put 'opencc-replace-at-point 'interactive-only 'opencc-string) 210 | 211 | ;;;###autoload 212 | (defun opencc-print-buffer (config &optional input-buffer output-buffer) 213 | "一个交互命令,转化当前 Buffer 中的内容,在 *OpenCC Output* Buffer 中显示结果." 214 | (interactive 215 | (let ((config (completing-read 216 | "配置文件: " 217 | opencc-configuration-files))) 218 | (list config nil nil))) 219 | (unless input-buffer 220 | (setq input-buffer (current-buffer))) 221 | (unless output-buffer 222 | (setq output-buffer (get-buffer-create "*OpenCC Output*"))) 223 | (let ((result (with-current-buffer input-buffer 224 | (opencc-region config (point-min) (point-max))))) 225 | (with-current-buffer output-buffer 226 | (delete-region (point-min) (point-max)) 227 | (insert result) 228 | (display-buffer (current-buffer))))) 229 | 230 | ;; XXX 使用 `defsubst' 提高性能? 231 | (defun opencc-insert-mode--post-self-insert-hook () 232 | (let ((char last-command-event)) 233 | (when (aref (char-category-set char) ?c) 234 | (delete-char -1) 235 | (insert (opencc-string opencc-insert-mode-config (string char)))))) 236 | 237 | ;;;###autoload 238 | (define-minor-mode opencc-insert-mode 239 | "按照 `opencc-insert-mode-config' 转换并替换每一个输入的汉字." 240 | :global t 241 | :lighter opencc-insert-mode-lighter 242 | (if opencc-insert-mode 243 | (add-hook 'post-self-insert-hook #'opencc-insert-mode--post-self-insert-hook) 244 | (remove-hook 'post-self-insert-hook #'opencc-insert-mode--post-self-insert-hook))) 245 | 246 | (defvar opencc-isearch-string-cache '("" . "") 247 | "Cache for `opencc-isearch-search-fun'.") 248 | (make-variable-buffer-local 'opencc-isearch-string-cache) 249 | 250 | (defun opencc-isearch-string (string) 251 | "Prepare STRING for isearch. 252 | 253 | If STRING contains Chinese, convert it with `opencc-string' then 254 | return the result. Otherwise, return STRING. 255 | 256 | Also setup cache via `opencc-isearch-string-cache' because it looks 257 | `isearch-search-fun-function' is supposed to be called with the 258 | same input for multiple times in a short time." 259 | (if (equal string (car opencc-isearch-string-cache)) 260 | (cdr opencc-isearch-string-cache) 261 | (if (string-match-p "\\cc" string) 262 | (let ((result-string (opencc-string opencc-isearch-mode-config string))) 263 | (setq opencc-isearch-string-cache (cons string result-string)) 264 | result-string) 265 | (setq opencc-isearch-string-cache (cons string string)) 266 | string))) 267 | 268 | (defun opencc-isearch-search-fun () 269 | "Should be the value of `isearch-search-fun-function'." 270 | (lambda (string &rest args) 271 | (apply (isearch-search-fun-default) 272 | (opencc-isearch-string string) 273 | args))) 274 | 275 | (defun multi-isearch-search-fun@support-opencc (orig-fun) 276 | "Advice around `multi-isearch-search-fun' for OpenCC support." 277 | (lambda (string &rest args) 278 | (apply (funcall orig-fun) 279 | (opencc-isearch-string string) 280 | args))) 281 | 282 | ;;;###autoload 283 | (define-minor-mode opencc-isearch-mode 284 | "输入简体搜索繁体(可通过 `opencc-isearch-mode-config' 调整转换的方向)." 285 | :global t 286 | :lighter opencc-isearch-mode-lighter 287 | (if opencc-isearch-mode 288 | (progn 289 | (setq isearch-search-fun-function #'opencc-isearch-search-fun) 290 | ;; `advice-add' comes from `nadvice.el', added since Emacs-24.4 291 | (advice-add 'multi-isearch-search-fun 292 | :around #'multi-isearch-search-fun@support-opencc)) 293 | (setq isearch-search-fun-function #'isearch-search-fun-default) 294 | (advice-remove 'multi-isearch-search-fun 295 | #'multi-isearch-search-fun@support-opencc))) 296 | 297 | (provide 'opencc) 298 | ;;; opencc.el ends here 299 | --------------------------------------------------------------------------------