├── .yas
└── c-mode
│ └── efunc
├── .gitignore
├── test.el
├── changelog.md
├── .github
└── ISSUE_TEMPLATE
│ └── --------.md
├── Makefile
├── INSTALLATION.org
├── INSTALLATION_EN.org
├── rime-predicates.el
├── README.org
├── lib.c
├── README_EN.org
├── LICENSE
└── rime.el
/.yas/c-mode/efunc:
--------------------------------------------------------------------------------
1 | emacs_value
2 | $1 (emacs_env *env, ptrdiff_t nargs, emacs_value *args, void *data) {
3 | $0
4 | }
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled
2 | *.so
3 | *.dylib
4 | *.elc
5 | *.dll
6 |
7 | # Packaging
8 | .cask
9 |
10 | # Backup files
11 | *~
12 | \#*
13 |
14 | # Undo-tree save-files
15 | *.~undo-tree
16 |
17 | # ccls cache directory
18 | .ccls-cache
19 | *.eln
20 |
--------------------------------------------------------------------------------
/test.el:
--------------------------------------------------------------------------------
1 | (package-initialize)
2 |
3 | (require 'rime)
4 |
5 | (rime-compile-module)
6 |
7 | (setq default-input-method "rime"
8 | rime-show-candidate 'posframe)
9 |
10 | (toggle-input-method)
11 |
12 | (rime-lib-select-schema "terra_pinyin")
13 |
--------------------------------------------------------------------------------
/changelog.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## [1.0.4] - 2020-09-14
4 | ### Added:
5 | - (Experimental) Add custom variable rime-posframe-fixed-position.
6 | - Add a new predicate rime-predicate-in-code-string.
7 |
8 | ## [1.0.3] - 2020-06-08
9 |
10 | ### Added:
11 | - Support for cursor control keys in rime-translate-keybindings.
12 |
13 | ## [1.0.2] - 2020-05-02
14 |
15 | ### Added:
16 | - Add custom variable rime-show-preedit.
17 |
18 | ### Removed:
19 | - Messages for activate/deactivate.
20 |
21 | ## [1.0.0]
22 |
23 | First version
24 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/--------.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: 问题报告(中文)
3 | about: Create a report to help us improve
4 | title: "[BUG]"
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | **问题描述**
11 |
12 | 简明的问题描述
13 |
14 | **Emacs中的配置**
15 |
16 | ```
17 | (use-package rime
18 | :custom
19 | (default-input-method "rime"))
20 | ```
21 |
22 | **复现方式**(安装遇到问题时省略)
23 |
24 | 复现问题所需的操作
25 |
26 | **是否可在 emacs -Q 中复现**
27 |
28 | 是/否
29 |
30 | **截图**
31 |
32 | 展示错误的截图或动图
33 |
34 | **环境信息**
35 |
36 | - 操作系统:如 ArchLinux
37 | - Emacs版本:如 27.0.90
38 | - Librime版本:如 1.5.3
39 | - Librime安装方式:包管理器/手工编译
40 | - 安装方式:Git/Melpa/Straight/Quelpa
41 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | ifdef MODULE_FILE_SUFFIX
2 | SUFFIX = $(MODULE_FILE_SUFFIX)
3 | else
4 | SUFFIX = .so
5 | endif
6 |
7 | TARGET = librime-emacs$(SUFFIX)
8 |
9 | CC = gcc
10 |
11 | ifdef DEBUG
12 | CFLAGS = -fPIC -g -Wall
13 | else
14 | CFLAGS = -fPIC -O2 -Wall
15 | endif
16 |
17 | ifeq ($(SUFFIX), .dylib)
18 | LDFLAGS = -dynamiclib
19 | else
20 | LDFLAGS = -shared
21 | endif
22 |
23 | ifdef LIBRIME_ROOT
24 | CFLAGS += -I ${LIBRIME_ROOT}include/
25 | LDFLAGS += -L ${LIBRIME_ROOT}lib/ -Wl,-rpath ${LIBRIME_ROOT}lib/ -lrime
26 | else
27 | LDFLAGS += -lrime
28 | endif
29 |
30 | ifdef EMACS_MODULE_HEADER_ROOT
31 | CFLAGS += -I ${EMACS_MODULE_HEADER_ROOT}
32 | endif
33 |
34 | default: clean test
35 |
36 | clean:
37 | -rm -f $(TARGET)
38 |
39 | lib:
40 | $(CC) lib.c -o $(TARGET) $(CFLAGS) $(LDFLAGS)
41 |
42 | test:
43 | emacs -Q -L "$$(pwd)" -l test.el
44 |
--------------------------------------------------------------------------------
/INSTALLATION.org:
--------------------------------------------------------------------------------
1 | #+title: Emacs Rime 基本安装与使用方法
2 |
3 | ** 依赖
4 | - Emacs 26.1+ ,且需启用动态支持模块。
5 | - emacs-rime 会自动构建所需的动态模块,这需要 ~make~ 和 ~gcc~ 可用。
6 | *** 如何获得支持动态模块的 Emacs ?
7 | #+html:
8 | #+html: 切换折叠
9 | **** Linux
10 | Linux 各主要发行版自带 emacs 默认已启用动态模块支持。
11 |
12 | **** macOS
13 | ***** emacs-plus 默认启用 ~--with-modules~ 选项,使用 homebrew 安装命令如下:
14 | #+BEGIN_SRC shell
15 | brew tap d12frosted/emacs-plus
16 | brew install emacs-plus
17 | #+END_SRC
18 |
19 | ***** emacs-mac 安装时需要启用 ~--with-modules~ 选项,使用 homebrew 安装命令如下 :
20 | #+BEGIN_SRC shell
21 | brew tap railwaycat/emacsmacport
22 | brew install emacs-mac --with-modules
23 | #+END_SRC
24 |
25 | **** 手工编译
26 |
27 | 使用 ~--with-modules~ 选项.
28 |
29 | #+html:
30 |
31 | ** 安装 librime
32 |
33 | *** Linux
34 |
35 | **** ArchLinux/Manjaro
36 |
37 | #+begin_src bash
38 | sudo pacman -S librime
39 | #+end_src
40 |
41 | **** Debian/Ubuntu
42 |
43 | #+begin_src bash
44 | sudo apt install librime-dev
45 | #+end_src
46 |
47 | 请注意 ~librime-dev~ 的版本,如果在1.5.3以下,则需要自行编译。
48 |
49 | #+begin_src bash
50 | sudo apt install git build-essential cmake libboost-all-dev libgoogle-glog-dev libleveldb-dev libmarisa-dev libopencc-dev libyaml-cpp-dev libgtest-dev
51 | git clone https://github.com/rime/librime.git ~/.emacs.d/librime
52 | cd ~/.emacs.d/librime
53 | make
54 | sudo make install
55 | #+end_src
56 |
57 | **** Fedora
58 |
59 | #+begin_src bash
60 | sudo dnf install librime-devel emacs-devel
61 | #+end_src
62 |
63 | **** openSUSE
64 |
65 | #+begin_src bash
66 | sudo zypper install librime-devel emacs-el
67 | #+end_src
68 |
69 | *** macOS
70 | #+begin_src bash
71 | brew install librime
72 | #+end_src
73 |
74 | 如果是 Apple Silicon 芯片,设置 ~librime~ 目录
75 | #+begin_src emacs-lisp
76 | (setq rime-librime-root "/opt/homebrew")
77 | #+end_src
78 |
79 | *** Windows
80 |
81 | **** 使用 scoop
82 |
83 | 可以使用 [[https://scoop.sh][scoop]] 来安装自动构建所需的依赖。
84 |
85 | #+begin_src powershell
86 | scoop install gcc
87 | scoop bucket add wsw0108 https://github.com/wsw0108/scoop-bucket.git
88 | scoop install librime
89 | #+end_src
90 |
91 | **** 使用 msys2
92 |
93 | 使用 [[https://www.msys2.org/][msys2]] 构建所需的依赖。
94 |
95 | #+begin_src bash
96 | pacman -S pactoys base-devel
97 | pacboy -S librime:x librime-data:x emacs:x toolchain:x
98 | ln -s /mingw64/share/opencc/* /mingw64/share/rime-data/opencc # Fix the Simplified Chinese input
99 | #+end_src
100 |
101 | 在 mingw64 环境中启动 emacs 来获取正确的 =SHELL= 环境变量,用来在 Emacs 内构建 librime-emacs 以及 =MSYSTEM_PREFIX= 环境变量来设置 ~rime-share-data-dir~ 变量。
102 |
103 | *** 编译时无法找到 rime_api.h ?
104 | #+html:
105 | #+html: 切换折叠
106 | 必须设置 ~rime-librime-root~ 参照安装方法中的说明。
107 |
108 | #+html:
109 | *** 编译时无法找到 emacs-module.h ?
110 | #+html:
111 | #+html: 切换折叠
112 |
113 | 如果自己编译 Emacs 且没有安装到标准目录(/usr/, /usr/local/),
114 | *必须* 指定 ~rime-emacs-module-header-root~ 。
115 |
116 | 在 ~:custom~ 中加入如下内容.
117 |
118 | (假设将 Emacs 安装到了 ~/emacs)
119 |
120 | #+BEGIN_SRC emacs-lisp
121 | (rime-emacs-module-header-root "~/emacs/include")
122 | #+END_SRC
123 |
124 | #+html:
125 | ** 最小配置
126 |
127 | Emacs Rime 已发布到 Melpa 。
128 |
129 | #+begin_src emacs-lisp
130 | (use-package rime
131 | :custom
132 | (default-input-method "rime"))
133 | #+end_src
134 |
135 | *** 使用 Straight
136 | #+html:
137 | #+html: 切换折叠
138 | #+BEGIN_SRC emacs-lisp
139 | (use-package rime
140 | :straight (rime :type git
141 | :host github
142 | :repo "DogLooksGood/emacs-rime"
143 | :files ("*.el" "Makefile" "lib.c"))
144 | :custom
145 | (default-input-method "rime"))
146 | #+END_SRC
147 | #+html:
148 |
149 | *** 使用 Quelpa
150 | #+html:
151 | #+html: 切换折叠
152 | #+BEGIN_SRC emacs-lisp
153 | (use-package rime
154 | :quelpa (rime :fetcher github
155 | :repo "DogLooksGood/emacs-rime"
156 | :files ("*.el" "Makefile" "lib.c"))
157 | :custom
158 | (default-input-method "rime"))
159 | #+END_SRC
160 | #+html:
161 |
162 | *** 注意(macOS)
163 | *NOTE* 如果你在 macOS 上或是选择自己编译 librime (没有放在系统路径中), *必须* 指定 ~rime-librime-root~ 。
164 |
165 | 在 ~:custom~ 中加入如下内容.
166 |
167 | (假设将 librime 解压到了 ~/.emacs.d/librime)
168 |
169 | #+BEGIN_SRC emacs-lisp
170 | (rime-librime-root "~/.emacs.d/librime/dist")
171 | #+END_SRC
172 |
173 | ** 激活 Rime 输入法
174 |
175 | 使用 ~toggle-input-method~ 来激活,默认快捷键为 ~C-\~
176 |
--------------------------------------------------------------------------------
/INSTALLATION_EN.org:
--------------------------------------------------------------------------------
1 | #+title: Emacs Rime Installation
2 |
3 | ** Requirements
4 |
5 | Emacs 26.1+ with dynamic modules support, ~gcc~ and ~make~.
6 |
7 | emacs-rime will build dynamic module automatically.
8 | *** How to get Emacs with dynamic module support?
9 |
10 | - Linux
11 |
12 | Emacs included in major linux distributions has dynamic module support enabled by default.
13 |
14 | - macOS
15 |
16 | ~emacs-plus~ enables dynamic modules support by default. homebrew installation:
17 | #+BEGIN_SRC shell
18 | brew tap d12frosted/emacs-plus
19 | brew install emacs-plus
20 | #+END_SRC
21 |
22 | When installing ~emacs-mac~, you need to add ~--with-modules~ option. homebrew installation:
23 | #+BEGIN_SRC shell
24 | brew tap railwaycat/emacsmacport
25 | brew install emacs-mac --with-modules
26 | #+END_SRC
27 |
28 | - **Compile Emacs 26 manually**
29 |
30 | Use ~--with-modules~ option.
31 |
32 |
33 |
34 | ** Install librime
35 |
36 | *** Linux
37 |
38 | **** ArchLinux/Manjaro
39 |
40 | #+begin_src emacs-lisp
41 | sudo pacman -S librime
42 | #+end_src
43 |
44 | **** Debian/Ubuntu
45 |
46 | #+begin_src emacs-lisp
47 | sudo apt install librime-dev
48 | #+end_src
49 |
50 | Check the version of ~librime-dev~ in repositiory, if it's older than 1.5.3, you need compile it from source.
51 |
52 | #+begin_src emacs-lisp
53 | sudo apt install git build-essential cmake libboost-all-dev libgoogle-glog-dev libleveldb-dev libmarisa-dev libopencc-dev libyaml-cpp-dev libgtest-dev
54 | git clone https://github.com/rime/librime.git ~/.emacs.d/librime
55 | cd ~/.emacs.d/librime
56 | make
57 | sudo make install
58 | #+end_src
59 |
60 | **** Fedora
61 |
62 | #+begin_src emacs-lisp
63 | sudo dnf install librime-devel emacs-devel
64 | #+end_src
65 |
66 | **** openSUSE
67 |
68 | #+begin_src emacs-lisp
69 | sudo zypper install librime-devel emacs-el
70 | #+end_src
71 |
72 | *** macOS
73 | #+begin_src bash
74 | brew install librime
75 | #+end_src
76 |
77 | If you are using Apple Silicon, set the ~librime~ directory:
78 | #+begin_src emacs-lisp
79 | (setq rime-librime-root "/opt/homebrew")
80 | #+end_src
81 |
82 | *** Windows
83 |
84 | **** using scoop
85 |
86 | Install dependencies via [[https://scoop.sh][scoop]].
87 |
88 | #+begin_src powershell
89 | scoop install gcc
90 | scoop bucket add wsw0108 https://github.com/wsw0108/scoop-bucket.git
91 | scoop install librime
92 | #+end_src
93 |
94 | **** using msys2
95 |
96 | Install dependencies via [[https://www.msys2.org/][msys2]].
97 |
98 | #+begin_src bash
99 | pacman -S pactoys base-devel
100 | pacboy -S librime:x librime-data:x emacs:x toolchain:x
101 | ln -s /mingw64/share/opencc/* /mingw64/share/rime-data/opencc # Fix the Simplified Chinese input
102 | #+end_src
103 |
104 | Start emacs from mingw64's environment to get the correct =SHELL= environment variable for building librime-emacs inside Emacs and =MSYSTEM_PREFIX= for specifying the ~rime-share-data-dir~ variable.
105 |
106 | *** Can't find rime_api.h when compile?
107 |
108 | You *MUST* specify ~rime-librime-root~ in this case.
109 |
110 | Check Installation for how to set.
111 |
112 | *** Can't find emacs-module.h when compile?
113 |
114 | If you build Emacs by yourself and does not install to standard location,
115 | you *MUST* specify ~rime-emacs-module-header-root~.
116 |
117 | Put following in the ~:custom~ section.
118 |
119 | (Assuming you install Emacs to ~/emacs)
120 |
121 | #+BEGIN_SRC emacs-lisp
122 | (rime-emacs-module-header-root "~/emacs/include")
123 | #+END_SRC
124 |
125 | ** Minimal configuration
126 |
127 | #+BEGIN_SRC emacs-lisp
128 | (use-package rime
129 | :custom
130 | (default-input-method "rime"))
131 | #+END_SRC
132 |
133 | *** Use Straight
134 | #+BEGIN_SRC emacs-lisp
135 | (use-package rime
136 | :straight (rime :type git
137 | :host github
138 | :repo "DogLooksGood/emacs-rime"
139 | :files ("*.el" "Makefile" "lib.c"))
140 | :custom
141 | (default-input-method "rime"))
142 | #+END_SRC
143 |
144 | *** Use Quelpa
145 | #+BEGIN_SRC emacs-lisp
146 | (use-package rime
147 | :quelpa (rime :fetcher github
148 | :repo "DogLooksGood/emacs-rime"
149 | :files ("*.el" "Makefile" "lib.c"))
150 | :custom
151 | (default-input-method "rime"))
152 | #+END_SRC
153 |
154 | *** NOTICE (macOS)
155 | If you are on macOS or don't have librime in standard path,
156 | you *MUST* specify ~rime-librime-root~.
157 |
158 | Put following in the ~:custom~ section.
159 |
160 | (Assuming you unzip librime to ~/.emacs.d/librime)
161 |
162 | #+BEGIN_SRC emacs-lisp
163 | (rime-librime-root "~/.emacs.d/librime/dist")
164 | #+END_SRC
165 |
166 | ** Activate input method
167 |
168 | To activate Rime, use command ~toggle-input-method~ which is bound to ~C-\~ by default.
169 |
--------------------------------------------------------------------------------
/rime-predicates.el:
--------------------------------------------------------------------------------
1 | ;;; rime-predicates.el --- Predicates for emacs-rime to automatic input Chinese/English. -*- lexical-binding: t; -*-
2 | ;;; cnsunyour/chinese/rime-predicates.el
3 |
4 |
5 | ;;; Commentary:
6 | ;;
7 | ;; With these predicates, You can continuously input mixed Chinese and English
8 | ;; text with punctuation, only using the Spacebar and the Enter key to assist,
9 | ;; without the extra switch key.
10 | ;;
11 |
12 | ;;; Code:
13 |
14 | (defun rime-predicate-after-alphabet-char-p ()
15 | "If the cursor is after a alphabet character.
16 |
17 | Can be used in `rime-disable-predicates' and `rime-inline-predicates'."
18 | (and (> (point) (save-excursion (back-to-indentation) (point)))
19 | (let ((string (buffer-substring (point) (max (line-beginning-position) (- (point) 80)))))
20 | (string-match-p "[a-zA-Z][0-9\x21-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]*$" string))))
21 |
22 | (defun rime-predicate-after-ascii-char-p ()
23 | "If the cursor is after a ascii character.
24 |
25 | Can be used in `rime-disable-predicates' and `rime-inline-predicates'."
26 | (and (> (point) (save-excursion (back-to-indentation) (point)))
27 | (let ((string (buffer-substring (point) (max (line-beginning-position) (- (point) 80)))))
28 | (string-match-p "[a-zA-Z0-9\x21-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]$" string))))
29 |
30 | (defun rime-predicate-prog-in-code-p ()
31 | "If cursor is in code.
32 |
33 | Can be used in `rime-disable-predicates' and `rime-inline-predicates'."
34 | (and (derived-mode-p 'prog-mode 'conf-mode)
35 | (not (or (nth 3 (syntax-ppss))
36 | (nth 4 (syntax-ppss))))))
37 |
38 | (defun rime-predicate-evil-mode-p ()
39 | "Detect whether the current buffer is in `evil' state.
40 |
41 | Include `evil-normal-state' ,`evil-visual-state' ,
42 | `evil-motion-state' , `evil-operator-state'.
43 |
44 | Can be used in `rime-disable-predicates' and `rime-inline-predicates'."
45 | (and (fboundp 'evil-mode)
46 | (or (evil-normal-state-p)
47 | (evil-visual-state-p)
48 | (evil-motion-state-p)
49 | (evil-operator-state-p))))
50 |
51 | (defun rime-predicate-hydra-p ()
52 | "Whether a hydra keymap is activated.
53 |
54 | Can be used in `rime-disable-predicates' and `rime-inline-predicates'."
55 | (and (featurep 'hydra)
56 | (bound-and-true-p hydra-curr-map)))
57 |
58 | (defun rime-predicate-ace-window-p ()
59 | "Detect if the `ace-window-mode' is enabled.
60 |
61 | Can be used in `rime-disable-predicates' and `rime-inline-predicates'."
62 | (and (featurep 'ace-window)
63 | (bound-and-true-p ace-window-mode)))
64 |
65 | (defun rime-predicate-current-input-punctuation-p ()
66 | "If the current charactor entered is a punctuation.
67 |
68 | Can be used in `rime-disable-predicates' and `rime-inline-predicates'."
69 | (and rime--current-input-key
70 | (or (and (<= #x21 rime--current-input-key) (<= rime--current-input-key #x2f))
71 | (and (<= #x3a rime--current-input-key) (<= rime--current-input-key #x40))
72 | (and (<= #x5b rime--current-input-key) (<= rime--current-input-key #x60))
73 | (and (<= #x7b rime--current-input-key) (<= rime--current-input-key #x7f)))))
74 |
75 | (defun rime-predicate-punctuation-after-space-cc-p ()
76 | "If input a punctuation after a Chinese charactor with whitespace.
77 |
78 | Can be used in `rime-disable-predicates' and `rime-inline-predicates'."
79 | (and (> (point) (save-excursion (back-to-indentation) (point)))
80 | (rime-predicate-current-input-punctuation-p)
81 | (let ((string (buffer-substring (point) (max (line-beginning-position) (- (point) 80)))))
82 | (string-match-p "\\cc +$" string))))
83 |
84 | (defun rime-predicate-punctuation-after-ascii-p ()
85 | "If input a punctuation after a ascii charactor with whitespace.
86 |
87 | Can be used in `rime-disable-predicates' and `rime-inline-predicates'."
88 | (and (rime-predicate-current-input-punctuation-p)
89 | (rime-predicate-after-ascii-char-p)))
90 |
91 | (defun rime-predicate-punctuation-line-begin-p ()
92 | "Enter half-width punctuation at the beginning of the line.
93 |
94 | Detect whether the current cursor is at the beginning of a
95 | line and the character last inputted is symbol.
96 |
97 | Can be used in `rime-disable-predicates' and `rime-inline-predicates'."
98 | (and (<= (point) (save-excursion (back-to-indentation) (point)))
99 | (rime-predicate-current-input-punctuation-p)))
100 |
101 | (defun rime-predicate-auto-english-p ()
102 | "Auto switch Chinese/English input state.
103 |
104 | After activating this probe function, use the following rules
105 | to automatically switch between Chinese and English input:
106 |
107 | 1. When the current character is an English
108 | character (excluding spaces), enter the next character as an
109 | English character.
110 | 2. When the current character is a Chinese character or the
111 | input character is a beginning character, the input character is
112 | a Chinese character.
113 | 3. With a single space as the boundary, automatically switch
114 | between Chinese and English characters.
115 |
116 | That is, a sentence of the form \"我使用 emacs 编辑此函数\"
117 | automatically switches between Chinese and English input methods.
118 |
119 | Can be used in `rime-disable-predicates' and `rime-inline-predicates'."
120 | (and (> (point) (save-excursion (back-to-indentation) (point)))
121 | (let ((string (buffer-substring (point) (max (line-beginning-position) (- (point) 80)))))
122 | (if (string-match-p " +$" string)
123 | (string-match-p "\\cc +$" string)
124 | (not (string-match-p "\\cc$" string))))))
125 |
126 | (defun rime-predicate-space-after-ascii-p ()
127 | "If cursor is after a whitespace which follow a ascii character."
128 | (and (> (point) (save-excursion (back-to-indentation) (point)))
129 | (let ((string (buffer-substring (point) (max (line-beginning-position) (- (point) 80)))))
130 | (and (string-match-p " +$" string)
131 | (not (string-match-p "\\cc +$" string))))))
132 |
133 | (defun rime-predicate-space-after-cc-p ()
134 | "If cursor is after a whitespace which follow a non-ascii character."
135 | (and (> (point) (save-excursion (back-to-indentation) (point)))
136 | (let ((string (buffer-substring (point) (max (line-beginning-position) (- (point) 80)))))
137 | (string-match-p "\\cc +$" string))))
138 |
139 | (defun rime-predicate-current-uppercase-letter-p ()
140 | "If the current charactor entered is a uppercase letter.
141 |
142 | Can be used in `rime-disable-predicates' and `rime-inline-predicates'."
143 | (and rime--current-input-key
144 | (>= rime--current-input-key ?A)
145 | (<= rime--current-input-key ?Z)))
146 |
147 | (defun rime-predicate-org-latex-mode-p ()
148 | "If cursor is inside an org-mode's LaTeX fragment, macro or its arguments."
149 | (and (derived-mode-p 'org-mode)
150 | (or (org-inside-LaTeX-fragment-p)
151 | (org-inside-latex-macro-p))))
152 |
153 | (defun rime-predicate-org-in-src-block-p ()
154 | "Whether point is in an org-mode's code source block."
155 | (and (derived-mode-p 'org-mode)
156 | (org-in-src-block-p)))
157 |
158 | (defun rime-predicate-in-code-string-p ()
159 | "Whether point is in the code string(not comment string)."
160 | (eq (plist-get (text-properties-at (point)) 'face) 'font-lock-string-face))
161 |
162 | (defun rime-predicate-in-code-string-after-ascii-p ()
163 | "Whether point is in the code string and after a ascii character."
164 | (and
165 | (eq (plist-get (text-properties-at (point)) 'face) 'font-lock-string-face)
166 | (rime-predicate-after-ascii-char-p)))
167 |
168 | (defun rime-predicate-tex-math-or-command-p ()
169 | "If point is inside a (La)TeX math environment, or a (La)TeX command.
170 |
171 | Return true if the buffer is in `tex-mode' and one of the following three cases occurs:
172 |
173 | 1. The point is inside a (La)TeX math environment;
174 | 2. The current character is `$' or `\\', either at the beginning of a line, or after an ascii/space.
175 | 3. The string before the point is of the form a (La)TeX command. If the command have a parameter, it is closed by `}' or `%'. If not, it is closed by a space or `%'.
176 |
177 | Can be used in `rime-disable-predicates' and `rime-inline-predicates'."
178 | (and (derived-mode-p 'tex-mode)
179 | (or (and (featurep 'tex-site)
180 | (texmathp))
181 | (and rime--current-input-key
182 | (or (= #x24 rime--current-input-key)
183 | (= #x5c rime--current-input-key))
184 | (or (= (point) (line-beginning-position))
185 | (= #x20 (char-before))
186 | (rime-predicate-after-ascii-char-p)))
187 | (and (> (point) (save-excursion (back-to-indentation) (point)))
188 | (let ((string (buffer-substring (point) (max (line-beginning-position) (- (point) 80)))))
189 | (or (string-match-p "[\x5c][\x21-\x24\x26-\x7e]*$" string)
190 | (string-match-p "[\x5c][a-zA-Z\x23\x40]+[\x7b][^\x7d\x25]*$" string)))))))
191 |
192 | (defun rime-predicate-punctuation-after-space-en-p ()
193 | "If input a punctuation after an English character with whitespace."
194 | (and (> (point) (save-excursion (back-to-indentation) (point)))
195 | (rime-predicate-current-input-punctuation-p)
196 | (let ((string (buffer-substring (point) (max (line-beginning-position) (- (point) 80)))))
197 | (string-match-p "[a-zA-Z] +$" string))))
198 |
199 | ;; Obsoleted functions:
200 | (define-obsolete-function-alias 'rime--after-alphabet-char-p 'rime-predicate-after-alphabet-char-p "2020-03-26")
201 | (define-obsolete-function-alias 'rime--prog-in-code-p 'rime-predicate-prog-in-code-p "2020-03-26")
202 | (define-obsolete-function-alias 'rime--evil-mode-p 'rime-predicate-evil-mode-p "2020-03-26")
203 | (define-obsolete-function-alias 'rime--punctuation-line-begin-p 'rime-predicate-punctuation-line-begin-p "2020-03-26")
204 | (define-obsolete-function-alias 'rime--auto-english-p 'rime-predicate-auto-english-p "2020-03-26")
205 | (make-obsolete 'rime-predicate-auto-english-p "please use other predicates instead of it." "2020-03-29")
206 |
207 |
208 | (provide 'rime-predicates)
209 |
210 | ;;; rime-predicates.el ends here
211 |
--------------------------------------------------------------------------------
/README.org:
--------------------------------------------------------------------------------
1 | #+TITLE: Emacs Rime
2 |
3 | [[https://melpa.org/#/rime][file:https://melpa.org/packages/rime-badge.svg]] [[https://stable.melpa.org/#/rime][file:https://stable.melpa.org/packages/rime-badge.svg]]
4 |
5 | [[file:README_EN.org][Document in English]]
6 |
7 | [[file:https://i.imgur.com/jHpk7BT.gif]]
8 |
9 | * 一、基本安装与使用方法
10 |
11 | 见 [[file:INSTALLATION.org][基本安装与使用方法]]
12 |
13 | * 二、Rime:方案配置与词库
14 | #+html:
15 | #+html: 切换折叠
16 | ** 设置输入中发送到 Rime 的组合键
17 | 通过变量 ~rime-translate-keybindings~ 可以设置哪些组合键将发送至 Rime 。可以用来配合方案中的设置完成光标移动和选字等功能。
18 | 支持 Control (C-), Meta (M-), Shift (S-)的组合键。
19 |
20 | #+BEGIN_SRC emacs-lisp
21 | ;; 默认值
22 | (setq rime-translate-keybindings
23 | '("C-f" "C-b" "C-n" "C-p" "C-g" "" "" "" "" "" "" ""))
24 | #+END_SRC
25 |
26 | ** 指定 Rime 共享目录和用户目录
27 |
28 | 共享目录即 ~rime-share-data-dir~ 是 Rime 安装后放置配置(包括输入方案)的目录,
29 | 例如 Linux 上默认为 =/usr/share/rime-data= ,通常使用默认值即可。
30 | 如果使用其它的位置,可以配置该值。
31 | 例如对于 fcitx5-rime 可能要使用 =~/.local/share/fcitx5/rime= 。
32 |
33 | 用户目录即 ~rime-user-data-dir~ 为 emacs-rime 布署的位置(包括词频等)。
34 | 默认为 =~/.emacs.d/rime= ,如果需要其它位置,可以配置该值。
35 |
36 | *不建议 ~emacs-rime~ 与 ~fcitx-rime~ 共用用户数据目录* 。
37 | 以地球拼音方案在 fcitx-rime 与 emacs-rime 中使用为例,若设置
38 | #+BEGIN_SRC emacs-lisp
39 | (setq rime-user-data-dir "~/.config/fcitx/rime/")
40 | #+END_SRC
41 | ,则在 ~emacs-rime~ 初次部署后,将会生成新的 ~terra_pinyin.userdb/~ 文件夹,原有 ~fcitx-rime~ 使用记录将会被移动到 ~terra_pinyin.userdb.old/~ ,此时新的 ~terra_pinyin.userdb.txt~ 中词频为空。
42 |
43 | ** 打开 Rime 的配置文件
44 |
45 | 使用 ~rime-open-configuration~ 打开自定义配置文件。
46 |
47 | 使用 ~rime-open-schema~ 打开一个方案的自定义配置文件。
48 |
49 | ** 重新部署
50 | #+html:
51 | #+html: 切换折叠
52 |
53 | ~emacs-rime~ 的配置文件更新之后,与 RIME 一样,都需要重新部署才可生效。
54 |
55 | 以添加 ~地球拼音(terra_pinyin)~ 为例。
56 |
57 | 找到 ~emacs-rime~ 配置所在路径,或使用 ~M-x rime-open-configuration~ 打开文件 ~default.custom.yaml~ ,在 ~patch:schema_list~ 中添加 ~- schema: terra_pinyin~ ,需要 ~M-x rime-deploy~ 重新部署才可启用地球拼音方案,重新部署成功后按 ~C-`~ 选择输入方案。
58 |
59 | 示例如下:
60 | #+BEGIN_SRC yaml
61 | patch:
62 | schema_list:
63 | - schema: luna_pinyin
64 | - schema: pinyin_simp
65 | - schema: terra_pinyin
66 | menu/page_size: 7 # 每页显示7个候选字词。
67 | switcher:
68 | hotkeys:
69 | - Control+grave # 激活RIME选单的快捷键,某些版本的RIME支持为快捷键,容易与其他软件冲突。
70 | #+END_SRC
71 |
72 | *** 重新部署后原有个人词库丢失?
73 |
74 | 这很可能是 ~emacs-rime~ 与 ~fcitx-rime~ 共用用户数据文件夹导致的。
75 |
76 | 如何找回:(依然以地球拼音为例)设置 ~emacs-rime~ 用户数据目录到其他文件夹,删除 ~terra_pinyin.userdb/~ 并将 ~terra_pinyin.userdb.old/~ 重命名为前者,再次同步或部署, ~terra_pinyin.userdb.txt~ 亦将恢复。
77 |
78 | #+html:
79 | ** 同步词库
80 | #+html:
81 | #+html: 切换折叠
82 |
83 | ~M-x rime-sync~ 可对 RIME 输入方案和词库进行同步与备份,每次同步双向进行,词库生成的备份文件为 ~sync/ins_id/schema.userdb.txt~ ,其本身是文件夹 ~schema.userdb/~ 中词库与词频使用记录的纯文本形式,方便用户跨平台、多设备使用。
84 |
85 | 所谓双向同步,即当前设备中的词频或用户自造词( ~schema.userdb/~ 中)与备份文件( ~sync/ins_id/schema.userdb.txt~ 中)所记录的词库会被 RIME 合并,其 *并集* 将会继续记录在 ~schema.userdb/~ 中,同时生成一份新的备份文件,仍名为 ~sync/ins_id/schema.userdb.txt~ ,并(在不询问用户的情况下)将旧的覆盖。
86 |
87 | 上述路径中 ~sync~ 文件夹与配置文件 ~default.custom.yaml~ 在同一目录, ~ins_id~ 对应的是 ~installation.yaml~ 文件中 ~installation_id~ 的值,默认值为随机生成,可自定义为其他字符串。
88 |
89 | 以添加 ~地球拼音(terra_pinyin)~ 后同步为例。启用该方案后,在 RIME 数据目录下会产生名为 ~terra_pinyin.userdb~ 的文件夹,其中为使用频率与自造词的记录,不可随意修改。同步前先修改 ~installation.yaml~ 中内容为自定义的 ~installation_id: "hesperus"~ ,之后 ~M-x rime-sync~ ,将会在 ~sync/hesperus/~ 生成文件 ~terra_pinyin.userdb.txt~ (词库)与 ~terra_pinyin.schema.yaml~ (输入方案)。
90 |
91 | 若在其他设备或系统中有个人积累的词库,想继续使用。则先在旧系统中进行同步,将生成的 ~terra_pinyin.userdb.txt~ 复制到当前系统的 ~sync/hesperus/~ 下,再进行同步或部署,此时旧系统中备份的词库将会被合并到当前系统的 ~terra_pinyin.userdb/~ ,新的并集也将会被同时导出,并覆盖 ~terra_pinyin.userdb.txt~ 。
92 |
93 | #+html:
94 | *** 词库同步失败?
95 | #+html:
96 | #+html: 切换折叠
97 |
98 | (以地球拼音方案使用为例。)
99 |
100 | *建议将不同设备或系统中的 ~installation_id~ 设为同一值* 。若其不同,则可能同步失败,即从旧系统同步并复制的 ~terra_pinyin.userdb.txt~ 中的词频记录不会被纳入到当前的 ~terra_pinyin.userdb/~ 。
101 | 此时该文件中词频不为空,但其中 ~user_id~ 等不同,修改此值后再次同步仍可能不生效。
102 | #+html:
103 | #+html:
104 | * 三、样式设置
105 | #+html:
106 | #+html: 切换折叠
107 | ** 候选框展示风格
108 |
109 | 设置 ~rime-show-candidate~ 。
110 |
111 | | 可选值 | 说明 |
112 | |------------+--------------------------------------------------------|
113 | | ~nil~ | 不展示 |
114 | | ~minibuffer~ | 在 minibuffer 中展示, 推荐使用的方式 |
115 | | ~message~ | 直接使用 ~message~ 输出,兼容控制 ~minibuffer~ 内容的插件 |
116 | | ~popup~ | 使用 ~popup.el~ 展示跟随的候选 |
117 | | ~posframe~ | 使用 ~posframe~ 展示跟随的候选,在不可用的时候会用 ~popup~ |
118 | | ~sidewindow~ | 使用 ~sidewindow~ 展示跟随的候选 |
119 |
120 | ** 候选样式
121 |
122 | | Face | 说明 |
123 | |-------------------------+------------------------------------|
124 | | ~rime-default-face~ | 默认的前景色和背景色(仅 posframe) |
125 | | ~rime-code-face~ | 编码的颜色 |
126 | | ~rime-candidate-num-face~ | 候选序号颜色 |
127 | | ~rime-comment-face~ | 编码提示颜色 |
128 |
129 | ** posframe/popup/sidewindow 候选版式
130 |
131 | 设置 ~rime-posframe-style~ , ~rime-popup-style~ 或者 ~rime-sidewindow-style~ 可选值有
132 |
133 | | 可选值 | 说明 |
134 | |------------+------------|
135 | | ~simple~ | 单行 |
136 | | ~horizontal~ | 水平,默认 |
137 | | ~vertical~ | 垂直 |
138 |
139 | ** posframe 的其它属性
140 |
141 | 设置 ~rime-posframe-properties~, 其中颜色的设置使用 ~rime-default-face~.
142 |
143 | #+begin_src emacs-lisp
144 | (setq rime-posframe-properties
145 | (list :font "sarasa ui sc"
146 | :internal-border-width 10))
147 | #+end_src
148 |
149 | 支持的内容参照 [[https://github.com/tumashu/posframe/blob/master/posframe.el#L212][posframe]] 。
150 |
151 | ** sidewindow 的其它屬性
152 |
153 | 设置 ~rime-sidewindow-style~ ,可选值有 ~top~, ~bottom~, ~left~, ~right~ ,分别指 sidewindow 出现的位置位于上下左右。
154 |
155 | 设置 ~rime-sidewindow-keep-window~ ,为 ~t~ 时可保持 sidewindow 为开启状态。
156 |
157 | ** 彩色指示标志
158 |
159 | 可用 ~(rime-lighter)~ 得到彩色指示标志 ~ㄓ~.
160 | 可将其放在 modeline 等任意地方。
161 |
162 | 可用 ~rime-title~ 、 ~rime-indicator-face~ 和 ~rime-indicator-dim-face~ 来自定义。
163 | ** 设置软光标的样式
164 |
165 | 默认使用 ~|~ 字符做为软光标,可以通过如下方式修改。
166 |
167 | #+BEGIN_SRC emacs-lisp
168 | (setq rime-cursor "˰")
169 | #+END_SRC
170 |
171 | 颜色可通过 ~rime-cursor-face~ 设置。
172 |
173 | ** 设置嵌入文本的样式
174 |
175 | 可通过 ~rime-preedit-face~ 设置。
176 |
177 | ** 编码的展示形式
178 |
179 | 设置 ~rime-show-preedit~, 可选值有
180 | | 可选值 | 说明 |
181 | |--------+--------------|
182 | | ~t~ | 展示在菜单中 |
183 | | ~inline~ | 替换上屏预览 |
184 | | ~nil~ | 不展示 |
185 |
186 | 注意:使用 ~inline~ 或 ~nil~ 将不再展示软光标。
187 |
188 | #+html:
189 | * 四、自动化设置
190 | #+html:
191 | #+html: 切换折叠
192 | ** 临时英文模式
193 | #+html:
194 | #+html: 切换折叠
195 | 如果使用模式编辑,或是在一些特定的场景下需要自动使用英文,可以设
196 | 置~rime-disable-predicates~ , ~rime-disable-predicates~ 的值是一个断言列表,
197 | 当其中有任何一个断言的值 **不是** nil 时,会自动使用英文。
198 |
199 | 一个在 ~evil-normal-state~ 中、在英文字母后面以及代码中自动使用英文的例子。
200 |
201 | #+BEGIN_SRC emacs-lisp
202 | (setq rime-disable-predicates
203 | '(rime-predicate-evil-mode-p
204 | rime-predicate-after-alphabet-char-p
205 | rime-predicate-prog-in-code-p))
206 | #+END_SRC
207 | *** 目前可用的断言函数
208 | #+html:
209 | #+html: 切换折叠
210 |
211 | - ~rime-predicate-after-alphabet-char-p~
212 |
213 | 在英文字符串之后(必须为以字母开头的英文字符串)
214 |
215 | - ~rime-predicate-after-ascii-char-p~
216 |
217 | 任意英文字符后
218 |
219 | - ~rime-predicate-prog-in-code-p~
220 |
221 | 在 ~prog-mode~ 和 ~conf-mode~ 中除了注释和引号内字符串之外的区域
222 |
223 | - ~rime-predicate-in-code-string-p~
224 |
225 | 在代码的字符串中,不含注释的字符串。
226 |
227 | - ~rime-predicate-evil-mode-p~
228 |
229 | 在 ~evil-mode~ 的非编辑状态下
230 |
231 | - ~rime-predicate-ace-window-p~
232 |
233 | 激活 ~ace-window-mode~
234 |
235 | - ~rime-predicate-hydra-p~
236 |
237 | 如果激活了一个 ~hydra~ keymap
238 |
239 | - ~rime-predicate-current-input-punctuation-p~
240 |
241 | 当要输入的是符号时
242 |
243 | - ~rime-predicate-punctuation-after-space-cc-p~
244 |
245 | 当要在中文字符且有空格之后输入符号时
246 |
247 | - ~rime-predicate-punctuation-after-ascii-p~
248 |
249 | 当要在任意英文字符之后输入符号时
250 |
251 | - ~rime-predicate-punctuation-line-begin-p~
252 |
253 | 在行首要输入符号时
254 |
255 | - ~rime-predicate-space-after-ascii-p~
256 |
257 | 在任意英文字符且有空格之后
258 |
259 | - ~rime-predicate-space-after-cc-p~
260 |
261 | 在中文字符且有空格之后
262 |
263 | - ~rime-predicate-current-uppercase-letter-p~
264 |
265 | 将要输入的为大写字母时
266 |
267 | - ~rime-predicate-tex-math-or-command-p~
268 |
269 | 在 (La)TeX 数学环境中或者输入 (La)TeX 命令时
270 |
271 | #+html:
272 | *** 可提示临时英文状态的提示符
273 |
274 | 使用函数 ~(rime-lighter)~ 返回一个用于展示的 ~ㄓ~ 符号。
275 | 可以通过 ~rime-indicator-face~ 和 ~rime-indicator-dim-face~ 设置样式。
276 |
277 | 如下设置可替换输入法的符号,使其用颜色提示当前的临时英文状态。
278 |
279 | #+begin_src emacs-lisp
280 | ;;; 具体参考 mode-line-mule-info 默认值,其中可能有其它有用信息
281 | (setq mode-line-mule-info '((:eval (rime-lighter))))
282 | #+end_src
283 |
284 | *** 基于 Rime inline ascii 模式的临时英文
285 |
286 | 设置 ~rime-inline-predicates~ ,结构与 ~rime-disable-predicates~ 相同,具有较低优先级。
287 |
288 | 这个功能主要用来实现输入带空格的临时英文的场景。
289 |
290 | 由于当前实现限制,如果 Rime 配置中没有使用默认的 ~Shift_L~ 切换 inline ascii 模式,需要在 emacs-rime 中指定。
291 | 两边配置相同才能正常激活。
292 |
293 | #+begin_src emacs-lisp
294 | ;;; support shift-l, shift-r, control-l, control-r
295 | (setq rime-inline-ascii-trigger 'shift-l)
296 | #+end_src
297 |
298 | 在有编码的状态下使用 ~rime-inline-ascii~ 命令可以切换状态。
299 |
300 | #+begin_src emacs-lisp
301 | (define-key rime-active-mode-map (kbd "M-j") 'rime-inline-ascii)
302 | #+end_src
303 |
304 | *** 临时英文中阻止标点直接上屏
305 | #+begin_src emacs-lisp
306 | (setq rime-inline-ascii-holder ?x) ; Any single character that not trigger auto commit
307 | #+end_src
308 |
309 | *** 断言成立时的强制中文模式
310 | 使用 ~rime-force-enable~ 来临时强制使用强制中文模式(即无视 ~rime-disable-predicates~ 中的规则),
311 | 在 *一次输入行为* 或 *取消输入* 之后会自动关闭强制中文模式。
312 |
313 | 你可能需要给这个命令绑定一个按键来使用。
314 |
315 | #+begin_src emacs-lisp
316 | (define-key rime-mode-map (kbd "M-j") 'rime-force-enable)
317 | #+end_src
318 |
319 | #+html:
320 | ** Commit1 自动上屏
321 | emacs-rime 内置了 =rime-commit1= 函数,其作用是在输入状态下将候选框中的首项自动上屏。
322 |
323 | 这对于不注重(或几乎不需要)选重、且码长可不固定(即简码)的输入方案,如纯形码、音形码等(尤其是顶功类方案)而言,非常有用。
324 |
325 | 此函数不是针对单独使用的,而主要是为了配合自定义其它函数,以适应个人的各种使用场景。
326 | *** 实例:在切换输入法时首项自动上屏
327 | 在切换输入法时首项自动上屏,这是 fcitx5-rime 等所支持的功能。
328 | 这里就以在 emacs-rime 中实现相同功能为例,说明如何使用 =rime-commit1= 函数。
329 |
330 | 基本思路是,先自定义一个函数,功能是先使得首项自动上屏,再切换输入法;
331 | 再将这个函数绑定到某个按键组合上,来替代原本 ~toggle-input-method~ 的功能。
332 |
333 | 示例配置如下:
334 | #+begin_src elisp
335 | (defun rime-commit1-and-toggle-input-method ()
336 | "Commit the 1st item if exists, then toggle input method."
337 | (interactive)
338 | (ignore-errors (rime-commit1))
339 | (toggle-input-method))
340 |
341 | (global-set-key (kbd "C-;") #'rime-commit1-and-toggle-input-method)
342 | #+end_src
343 | 这里使用了 =ignore-errors= 来防止在初次切换输入法时 =rime-commit1= 未被加载而报错。
344 | 在此配置生效后,按 =C-;= 即可使首项自动上屏并切换输入法。
345 | *** 对任意无关函数首项自动上屏
346 | 若你有很多函数需要在其执行前首项自动上屏,而又不想对它们一个个进行自定义函数与绑定的配置,你可以设置对任意与 emacs-rime 无关函数的首项自动上屏。
347 |
348 | 机制:
349 | - 在 emacs-rime 的输入状态下,若执行与 emacs-rime 无关的函数,默认会触发 =rime--clear-state= 清空候选框。
350 | - 若设置变量 =rime-commit1-forall= 为一非 nil 值,则会将触发的函数改为 =rime-commit1= 即首项自动上屏。
351 |
352 | 示例配置如下:
353 | #+begin_src elisp
354 | (setq rime-commit1-forall t)
355 | #+end_src
356 | *** 按 ESC 时首项自动上屏
357 | 上述全局设置,不能使得在输入时按 ESC 时首项也自动上屏,这里给出针对性的配置方法。
358 |
359 | 机制:在 emacs-rime 的输入状态下,ESC 被绑定到了 =rime--escape= 函数上,此函数显然不是“与 emacs-rime 无关的函数”。
360 |
361 | 实例:以 evil 插件为例,若要使得 ESC 能自动上屏并切换到 evil 的 normal state,可以先自定义函数,并将此函数绑定到 ESC 上。
362 | (注:至少对于 evil 插件而言, =C-[= 和 =ESC= 被 emacs 当作同一按键,所以不需要对 =C-[= 另行绑键。)
363 |
364 | 示例配置如下:
365 | #+begin_src elisp
366 | (defun rime-commit1-and-evil-normal ()
367 | "Commit the 1st item if exists, then go to evil normal state."
368 | (interactive)
369 | (rime-commit1)
370 | (evil-normal-state))
371 | (define-key rime-active-mode-map (kbd "") 'rime-commit1-and-evil-normal)
372 | #+end_src
373 | ** 在 minibuffer 使用后自动关闭输入法
374 |
375 | 默认行为为自动关闭,设置 ~rime-deactivate-when-exit-minibuffer~ 为 nil 取消该行为。
376 |
377 | ** 结合 evil-escape 一起使用
378 | #+html:
379 | #+html: 切换折叠
380 | *以下代码可能有性能问题*
381 |
382 | 在你的配置中添加如下内容,即可在当前没有输入内容(没有 preedit overlay)的情况
383 | 下,用[[https://github.com/syl20bnr/evil-escape][evil-escape]]的按键回到 normal 模式。
384 |
385 | #+BEGIN_SRC emacs-lisp
386 | (defun rime-evil-escape-advice (orig-fun key)
387 | "advice for `rime-input-method' to make it work together with `evil-escape'.
388 | Mainly modified from `evil-escape-pre-command-hook'"
389 | (if rime--preedit-overlay
390 | ;; if `rime--preedit-overlay' is non-nil, then we are editing something, do not abort
391 | (apply orig-fun (list key))
392 | (when (featurep 'evil-escape)
393 | (let (
394 | (fkey (elt evil-escape-key-sequence 0))
395 | (skey (elt evil-escape-key-sequence 1))
396 | )
397 | (if (or (char-equal key fkey)
398 | (and evil-escape-unordered-key-sequence
399 | (char-equal key skey)))
400 | (let ((evt (read-event nil nil evil-escape-delay)))
401 | (cond
402 | ((and (characterp evt)
403 | (or (and (char-equal key fkey) (char-equal evt skey))
404 | (and evil-escape-unordered-key-sequence
405 | (char-equal key skey) (char-equal evt fkey))))
406 | (evil-repeat-stop)
407 | (evil-normal-state))
408 | ((null evt) (apply orig-fun (list key)))
409 | (t
410 | (apply orig-fun (list key))
411 | (if (numberp evt)
412 | (apply orig-fun (list evt))
413 | (setq unread-command-events (append unread-command-events (list evt))))))
414 | )
415 | (apply orig-fun (list key)))))))
416 |
417 | (advice-add 'rime-input-method :around #'rime-evil-escape-advice)
418 | #+END_SRC
419 | #+html:
420 |
421 | #+html:
422 | * 五、杂项
423 | #+html:
424 | #+html: 切换折叠
425 | ** 打开 Rime 菜单
426 |
427 | 假设你为菜单使用 ~C-~~ 。
428 |
429 | #+begin_src yaml
430 | switcher:
431 | caption: 〔方案選單〕
432 | hotkeys:
433 | - Control+grave
434 | #+end_src
435 |
436 | 你可用函数 ~rime-send-keybinding~ 将此按键绑定到 ~rime-mode-map~ 。
437 |
438 | #+begin_src emacs-lisp
439 | (use-package
440 | ...
441 |
442 | :bind
443 | (:map rime-mode-map
444 | ("C-`" . 'rime-send-keybinding))
445 | ...
446 | )
447 | #+end_src
448 |
449 | #+html:
450 | * 六、FAQ
451 | #+html:
452 | #+html: 切换折叠
453 | ** 在 isearch 中的使用
454 |
455 | 目前在 isearch 中不能正常工作,但是可以使用 [[https://github.com/zk-phi/phi-search][phi-search]].
456 |
457 | ** 候选框最后一项不显示?
458 | #+html:
459 | #+html: 切换折叠
460 | 极少数用户下会偶尔出现最后一个候选词不显示的情况,可以确定跟 `posframe` 有关,但
461 | 目前尚未找到原因,有一个暂时的解决办法,就是给候选词列表最后附加一个全角空格,这
462 | 样即使出现“吃字”的情况也只是把末尾的全角空格“吃”掉,不会影响候选词的显示。
463 | 代码如下:
464 | #+BEGIN_SRC emacs-lisp
465 | (defun +rime--posframe-display-content-a (args)
466 | "给 `rime--posframe-display-content' 传入的字符串加一个全角空
467 | 格,以解决 `posframe' 偶尔吃字的问题。"
468 | (cl-destructuring-bind (content) args
469 | (let ((newresult (if (string-blank-p content)
470 | content
471 | (concat content " "))))
472 | (list newresult))))
473 |
474 | (if (fboundp 'rime--posframe-display-content)
475 | (advice-add 'rime--posframe-display-content
476 | :filter-args
477 | #'+rime--posframe-display-content-a)
478 | (error "Function `rime--posframe-display-content' is not available."))
479 | #+END_SRC
480 | #+html:
481 |
482 | ** 无需 librime 纯 Emacs 实现的输入法?
483 |
484 | 你可能需要 [[https://github.com/tumashu/pyim][pyim]].
485 |
486 | #+html:
487 | * 终、感谢所有的 Contributor
488 |
489 | - [[https://github.com/Z572][Z572]]
490 | - [[https://github.com/cnsunyour][cnsunyour]]
491 | - [[https://github.com/shuxiao9058][shuxiao9058]]
492 | - [[https://github.com/lkzz][lkzz]]
493 | - [[https://github.com/wsw0108][wsw0108]]
494 | - [[https://github.com/HesperusArcher][HesperusArcher]]
495 | - [[https://github.com/longminwang][longminwang]]
496 | - [[https://github.com/chuxubank][chuxubank]]
497 | - [[https://github.com/jixiuf][jixiuf]]
498 | - [[https://github.com/cireu][cireu]]
499 | - [[https://github.com/ilupin][ilupin]]
500 | - [[https://github.com/dwuggh][dwuggh]]
501 | - [[https://github.com/zilongshanren][zilongshanren]]
502 | - [[https://github.com/zhmars][zhmars]]
503 | - [[https://github.com/syohex][syohex]]
504 | - [[https://github.com/pmeiyu][pmeiyu]]
505 | - [[https://github.com/p1uxtar][p1uxtar]]
506 | - [[https://github.com/gemone][gemone]]
507 | - [[https://github.com/casouri][casouri]]
508 | - [[https://github.com/Tubo][Tubo]]
509 | - [[https://github.com/Eason0210][Eason0210]]
510 | - [[https://github.com/wang1zhen][wang1zhen]]
511 | - [[https://github.com/shenlebantongying][shenlebantongying]]
512 | - [[https://github.com/nasyxx][nasyxx]]
513 |
--------------------------------------------------------------------------------
/lib.c:
--------------------------------------------------------------------------------
1 | // Author: Shi Tianshu
2 | //
3 | // This file is not part of GNU Emacs.
4 | //
5 | // This program is free software; you can redistribute it and/or
6 | // modify it under the terms of the GNU General Public License
7 | // as published by the Free Software Foundation; either version 3
8 | // of the License, or (at your option) any later version.
9 | //
10 | // This program is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with GNU Emacs; see the file COPYING. If not, write to the
17 | // Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 | // Boston, MA 02110-1301, USA.
19 |
20 | #include
21 | #include
22 | #include
23 | #include
24 | #include
25 |
26 | #define INTERN(val) env->intern(env, val)
27 | #define GLOBAL_REF(val) env->make_global_ref(env, val)
28 | #define REF(val) env->make_global_ref(env, env->intern(env, val))
29 | #define STRING(val) env->make_string(env, val, strlen(val))
30 | #define FUNCALL0(func) env->funcall(env, func, 0, NULL)
31 | #define FUNCALL1(func, a) env->funcall(env, func, 1, (emacs_value[]){a})
32 | #define FUNCALL2(func, a, b) env->funcall(env, func, 2, (emacs_value[]){a, b})
33 | #define CONS(car, cdr) FUNCALL2(REF("cons"), car, cdr)
34 | #define INT(val) env->make_integer(env, val)
35 | #define LIST(len, array) env->funcall(env, REF("list"), len, array)
36 |
37 | int plugin_is_GPL_compatible;
38 |
39 | emacs_value nil, t;
40 |
41 | typedef struct _EmacsRime {
42 | RimeSessionId session_id;
43 | RimeApi *api;
44 | bool first_run;
45 | } EmacsRime;
46 |
47 | static char *copy_string(char *str) {
48 | if (str) {
49 | size_t size = strlen(str);
50 | char *new_str = malloc((size + 1) * sizeof(char));
51 | strcpy(new_str, str);
52 | new_str[size] = '\0';
53 | return new_str;
54 | } else {
55 | return NULL;
56 | }
57 | }
58 |
59 | char *get_string(emacs_env *env, emacs_value arg)
60 | {
61 | if (arg == NULL) {
62 | return NULL;
63 | } else {
64 | ptrdiff_t size;
65 | env->copy_string_contents(env, arg, NULL, &size);
66 | char *buf = (char*) malloc(size * sizeof(char));
67 | env->copy_string_contents(env, arg, buf, &size);
68 | return buf;
69 | }
70 | }
71 |
72 | void notification_handler(void *context,
73 | RimeSessionId session_id,
74 | const char *message_type,
75 | const char *message_value) {
76 | }
77 |
78 |
79 | emacs_value
80 | string_length(emacs_env *env, ptrdiff_t nargs, emacs_value args[], void *data) {
81 | char* str = get_string(env, args[0]);
82 | int len = strlen(str);
83 | return INT(len);
84 | }
85 |
86 | emacs_value
87 | finalize(emacs_env *env, ptrdiff_t nargs, emacs_value args[], void *data) {
88 | EmacsRime *rime = (EmacsRime*) data;
89 | if (rime->session_id) {
90 | rime->session_id = 0;
91 | }
92 | rime->api->finalize();
93 | return t;
94 | }
95 |
96 | emacs_value
97 | get_sync_dir(emacs_env *env, ptrdiff_t nargs, emacs_value args[], void *data) {
98 | EmacsRime *rime = (EmacsRime*) data;
99 |
100 | const char *sync_dir = rime->api->get_sync_dir();
101 | return env->make_string(env, sync_dir, strlen(sync_dir));
102 | }
103 |
104 | emacs_value
105 | sync_user_data(emacs_env *env, ptrdiff_t nargs, emacs_value args[], void *data) {
106 | EmacsRime *rime = (EmacsRime*) data;
107 |
108 | bool result = rime->api->sync_user_data();
109 | return result ? t : nil;
110 | }
111 |
112 | emacs_value
113 | start(emacs_env *env, ptrdiff_t nargs, emacs_value args[], void *data) {
114 | EmacsRime *rime = (EmacsRime*) data;
115 |
116 | char *shared_data_dir = get_string(env, args[0]);
117 | char *user_data_dir = get_string(env, args[1]);
118 |
119 | RIME_STRUCT(RimeTraits, emacs_rime_traits);
120 |
121 | emacs_rime_traits.shared_data_dir = shared_data_dir;
122 | emacs_rime_traits.app_name = "rime.emacs";
123 | emacs_rime_traits.user_data_dir = user_data_dir;
124 | emacs_rime_traits.distribution_name = "Rime";
125 | emacs_rime_traits.distribution_code_name = "emacs-rime";
126 | emacs_rime_traits.distribution_version = "1.0.1";
127 | if (rime->first_run) {
128 | rime->api->setup(&emacs_rime_traits);
129 | rime->first_run = false;
130 | }
131 |
132 | rime->api->initialize(&emacs_rime_traits);
133 | rime->api->set_notification_handler(notification_handler, rime);
134 | rime->api->start_maintenance(true);
135 |
136 | // wait for deploy
137 | rime->api->join_maintenance_thread();
138 |
139 | rime->session_id = rime->api->create_session();
140 |
141 | return t;
142 | }
143 |
144 |
145 | emacs_value
146 | get_option (emacs_env *env, ptrdiff_t nargs, emacs_value *args, void *data) {
147 | EmacsRime *rime = (EmacsRime*) data;
148 |
149 | char *option = get_string(env, args[0]);
150 | if (rime->api->get_option(rime->session_id, option)) {
151 | return t;
152 | }
153 | return nil;
154 | }
155 |
156 |
157 | emacs_value
158 | set_option (emacs_env *env, ptrdiff_t nargs, emacs_value *args, void *data) {
159 | EmacsRime *rime = (EmacsRime*) data;
160 |
161 | char *option = get_string(env, args[0]);
162 | bool value = env->is_not_nil(env, args[1]);
163 |
164 | rime->api->set_option(rime->session_id, option, value);
165 |
166 | return nil;
167 | }
168 |
169 | emacs_value
170 | process_key(emacs_env *env, ptrdiff_t nargs, emacs_value *args, void *data) {
171 | EmacsRime *rime = (EmacsRime*) data;
172 |
173 | int keycode = env->extract_integer(env, args[0]);
174 | int mask = env->extract_integer(env, args[1]);
175 |
176 | if (rime->api->process_key(rime->session_id, keycode, mask)) {
177 | return t;
178 | }
179 | return nil;
180 | }
181 |
182 | emacs_value
183 | get_context(emacs_env *env, ptrdiff_t nargs, emacs_value *args, void *data) {
184 | EmacsRime *rime = (EmacsRime*) data;
185 |
186 | RIME_STRUCT(RimeContext, context);
187 | if (!rime->api->get_context(rime->session_id, &context)){
188 | return nil;
189 | }
190 |
191 | size_t result_size = 4;
192 | emacs_value result_a[result_size];
193 |
194 | // 0. context.commit_text_preview
195 | char *ctp_str = copy_string(context.commit_text_preview);
196 | if (ctp_str)
197 | result_a[0] = CONS(INTERN("commit-text-preview"), STRING(ctp_str));
198 | else
199 | result_a[0] = CONS(INTERN("commit-text-preview"), nil);
200 |
201 | // 1. context.composition
202 | emacs_value composition_a[7];
203 |
204 | int length = context.composition.length;
205 | int cursor_pos = context.composition.cursor_pos;
206 |
207 | composition_a[0] = CONS(INTERN("length"), INT(length));
208 | composition_a[1] = CONS(INTERN("cursor-pos"), INT(cursor_pos));
209 | composition_a[2] = CONS(INTERN("sel-start"), INT(context.composition.sel_start));
210 | composition_a[3] = CONS(INTERN("sel-end"), INT(context.composition.sel_end));
211 |
212 | char *preedit_str = copy_string(context.composition.preedit);
213 | if (preedit_str) {
214 | composition_a[4] = CONS(INTERN("preedit"), STRING(preedit_str));
215 |
216 | int before_cursor_len = cursor_pos;
217 | int after_cursor_len = length - cursor_pos;
218 |
219 | char* before_cursor = malloc(before_cursor_len + 1);
220 | char* after_cursor = malloc(after_cursor_len + 1);
221 |
222 | strncpy(before_cursor, preedit_str, before_cursor_len);
223 | strncpy(after_cursor, preedit_str + before_cursor_len, after_cursor_len);
224 |
225 | before_cursor[before_cursor_len] = '\0';
226 | after_cursor[after_cursor_len] = '\0';
227 |
228 | composition_a[5] = CONS(INTERN("before-cursor"), STRING(before_cursor));
229 | composition_a[6] = CONS(INTERN("after-cursor"), STRING(after_cursor));
230 |
231 | free(before_cursor);
232 | free(after_cursor);
233 |
234 | } else {
235 | composition_a[4] = CONS(INTERN("preedit"), nil);
236 | composition_a[5] = CONS(INTERN("before-cursor"), nil);
237 | composition_a[6] = CONS(INTERN("after-cursor"), nil);
238 | }
239 |
240 | emacs_value composition_value = LIST(7, composition_a);
241 | result_a[1] = CONS(INTERN("composition"), composition_value);
242 |
243 | // 2. context.menu
244 | if (context.menu.num_candidates) {
245 | emacs_value menu_a[7];
246 | menu_a[0] = CONS(INTERN("highlighted-candidate-index"), INT(context.menu.highlighted_candidate_index));
247 | menu_a[1] = CONS(INTERN("last-page-p"), context.menu.is_last_page ? t : nil);
248 | menu_a[2] = CONS(INTERN("num-candidates"), INT(context.menu.num_candidates));
249 | menu_a[3] = CONS(INTERN("page-no"), INT(context.menu.page_no));
250 | menu_a[4] = CONS(INTERN("page-size"), INT(context.menu.page_size));
251 | menu_a[5] = CONS(INTERN("highlighted-candidate-index"), INT(context.menu.highlighted_candidate_index));
252 | emacs_value carray[context.menu.num_candidates];
253 | // Build candidates
254 | for (int i = 0; i < context.menu.num_candidates; i++) {
255 | RimeCandidate c = context.menu.candidates[i];
256 | char *ctext = copy_string(c.text);
257 | char *comment_text = copy_string(c.comment);
258 | emacs_value comment;
259 | if (comment_text) {
260 | comment = STRING(comment_text);
261 | } else {
262 | comment = nil;
263 | }
264 | carray[i] = CONS(STRING(ctext), comment);
265 | }
266 | emacs_value candidates = LIST(context.menu.num_candidates, carray);
267 | menu_a[6] = CONS(INTERN("candidates"), candidates);
268 | emacs_value menu = LIST(7, menu_a);
269 | result_a[2] = CONS(INTERN("menu"), menu);
270 | } else {
271 | result_a[2] = CONS(INTERN("menu"), nil);
272 | }
273 |
274 | // 3. context.select_labels
275 | size_t page_size = context.menu.page_size;
276 | if (context.select_labels && page_size > 0) {
277 | emacs_value select_labels_a[page_size];
278 | for (int i = 0; i < page_size; i++) {
279 | char* label = context.select_labels[i];
280 | select_labels_a[i] = STRING(label);
281 | }
282 | result_a[3] = CONS(INTERN("select-labels"), LIST(page_size, select_labels_a));
283 | } else {
284 | result_a[3] = CONS(INTERN("select-labels"), nil);
285 | }
286 |
287 | // build result
288 | emacs_value result = LIST(result_size, result_a);
289 |
290 | rime->api->free_context(&context);
291 |
292 | return result;
293 | }
294 |
295 | emacs_value
296 | version(emacs_env *env, ptrdiff_t nargs, emacs_value *args, void *data) {
297 | return STRING("1.0.5");
298 | }
299 |
300 |
301 | emacs_value
302 | set_cursor_pos(emacs_env *env, ptrdiff_t nargs, emacs_value *args, void *data) {
303 | EmacsRime *rime = (EmacsRime*) data;
304 | int pos = env->extract_integer(env, args[0]);
305 |
306 | rime->api->set_caret_pos(rime->session_id, pos);
307 |
308 | return nil;
309 | }
310 |
311 |
312 | emacs_value
313 | clear_composition(emacs_env *env, ptrdiff_t nargs, emacs_value *args, void *data) {
314 | EmacsRime *rime = (EmacsRime*) data;
315 |
316 | rime->api->clear_composition(rime->session_id);
317 | return t;
318 | }
319 |
320 | emacs_value
321 | get_input (emacs_env *env, ptrdiff_t nargs, emacs_value *args, void *data) {
322 | EmacsRime *rime = (EmacsRime*) data;
323 |
324 | const char* input = rime->api->get_input(rime->session_id);
325 |
326 | if (!input) {
327 | return nil;
328 | } else {
329 | return STRING(input);
330 | }
331 | }
332 |
333 | emacs_value
334 | get_commit(emacs_env *env, ptrdiff_t nargs, emacs_value *args, void *data) {
335 | EmacsRime *rime = (EmacsRime*) data;
336 |
337 | RIME_STRUCT(RimeCommit, commit);
338 | if (rime->api->get_commit(rime->session_id, &commit)) {
339 | if (!commit.text) {
340 | return nil;
341 | }
342 |
343 | char *commit_str = copy_string(commit.text);
344 | rime->api->free_commit(&commit);
345 |
346 | return STRING(commit_str);
347 | }
348 |
349 | return nil;
350 | }
351 |
352 |
353 | emacs_value
354 | select_schema(emacs_env *env, ptrdiff_t nargs, emacs_value args[], void *data) {
355 | EmacsRime *rime = (EmacsRime*) data;
356 | const char *schema_id = get_string(env, args[0]);
357 | if (rime->api->select_schema(rime->session_id, schema_id)) {
358 | return t;
359 | }
360 | return nil;
361 | }
362 |
363 |
364 | emacs_value
365 | get_schema_list(emacs_env *env, ptrdiff_t nargs, emacs_value *args, void *data) {
366 | EmacsRime *rime = (EmacsRime*) data;
367 |
368 | RimeSchemaList schema_list;
369 |
370 | if (!rime->api->get_schema_list(&schema_list)) {
371 | return nil;
372 | }
373 |
374 | emacs_value flist = env->intern(env, "list");
375 | emacs_value array[schema_list.size];
376 |
377 | for (int i = 0; i < schema_list.size; i++) {
378 | RimeSchemaListItem item = schema_list.list[i];
379 | array[i] = FUNCALL2(INTERN("list"), STRING(item.schema_id), STRING(item.name));
380 | }
381 |
382 | emacs_value result = env->funcall(env, flist, schema_list.size, array);
383 |
384 | rime->api->free_schema_list(&schema_list);
385 |
386 | return result;
387 | }
388 |
389 | /*
390 | * Deprecated
391 | * Prefer user set how to toggle inline ascii.
392 | *
393 | *
394 | * Find which key is used for inline_ascii, and simulate.
395 | * This is a tricky implementation, it work only when:
396 | * - build/default.yaml is exists.
397 | * - one of Shift_L, Shift_R, Control_L, Control_R is used to trigger inline_ascii.
398 | *
399 | * TODO: Find a better way.
400 | */
401 | emacs_value
402 | inline_ascii(emacs_env *env, ptrdiff_t nargs, emacs_value *args, void *data) {
403 | EmacsRime *rime = (EmacsRime*) data;
404 |
405 | RimeConfig *conf = malloc(sizeof(RimeConfig));
406 |
407 | if(!rime->api->user_config_open("build/default.yaml", conf)) {
408 | free(conf);
409 | return nil;
410 | }
411 |
412 | char *buf = malloc(128 * sizeof(char));
413 | emacs_value result = nil;
414 |
415 | if (rime->api->config_get_string(conf, "ascii_composer/switch_key/Shift_L", buf, 128)
416 | && (strcmp(buf, "inline_ascii") == 0)) {
417 | rime->api->process_key(rime->session_id, 65505, 0);
418 | rime->api->process_key(rime->session_id, 65505, 1073741824);
419 | result = STRING("inline_ascii");
420 | } else if (rime->api->config_get_string(conf, "ascii_composer/switch_key/Shift_R", buf, 128)
421 | && (strcmp(buf, "inline_ascii") == 0)) {
422 | rime->api->process_key(rime->session_id, 65506, 0);
423 | rime->api->process_key(rime->session_id, 65506, 1073741824);
424 | result = STRING("inline_ascii");
425 | } else if (rime->api->config_get_string(conf, "ascii_composer/switch_key/Control_L", buf, 128)
426 | && (strcmp(buf, "inline_ascii") == 0)) {
427 | rime->api->process_key(rime->session_id, 65507, 0);
428 | rime->api->process_key(rime->session_id, 65507, 1073741824);
429 | result = STRING("inline_ascii");
430 | } else if (rime->api->config_get_string(conf, "ascii_composer/switch_key/Control_R", buf, 128)
431 | && (strcmp(buf, "inline_ascii") == 0)) {
432 | rime->api->process_key(rime->session_id, 65508, 0);
433 | rime->api->process_key(rime->session_id, 65508, 1073741824);
434 | result = STRING("inline_ascii");
435 | } else {
436 | result = nil;
437 | }
438 |
439 | free(buf);
440 |
441 | rime->api->config_close(conf);
442 |
443 | return result;
444 | }
445 |
446 |
447 | void
448 | emacs_defun(emacs_env *env, EmacsRime *rime, void* cfunc, char* func_name, char* doc, size_t min, size_t max) {
449 | emacs_value func = env->make_function(env, min, max, cfunc, doc, rime);
450 | FUNCALL2(REF("defalias"), REF(func_name), func);
451 | }
452 |
453 | int
454 | emacs_module_init (struct emacs_runtime *ert)
455 | {
456 | emacs_env *env = ert->get_environment(ert);
457 |
458 | /* Get Rime API */
459 | EmacsRime *rime = (EmacsRime*) malloc(sizeof(EmacsRime));
460 | rime->api = rime_get_api();
461 | if (!rime->api) {
462 | free(rime);
463 | return 0;
464 | }
465 | rime->first_run = true;
466 |
467 | /* Global emacs_value initialize */
468 | nil = REF("nil");
469 | t = REF("t");
470 | /* Make functions */
471 |
472 | emacs_defun(env, rime, version, "rime-lib-version", "Version", 0, 0);
473 | emacs_defun(env, rime, set_cursor_pos, "rime-lib-set-cursor-pos", "Set Cursor Pos", 1, 1);
474 | emacs_defun(env, rime, start, "rime-lib-start", "Start", 2, 2);
475 | emacs_defun(env, rime, finalize, "rime-lib-finalize", "Finalize", 0, 0);
476 | emacs_defun(env, rime, sync_user_data, "rime-lib-sync-user-data", "Sync user data.", 0, 0);
477 | emacs_defun(env, rime, get_sync_dir, "rime-lib-get-sync-dir", "Get sync directory.", 0, 0);
478 | emacs_defun(env, rime, get_context, "rime-lib-get-context", "Get context.", 0, 0);
479 | emacs_defun(env, rime, get_input, "rime-lib-get-input", "Get input.", 0, 0);
480 | emacs_defun(env, rime, get_commit, "rime-lib-get-commit", "Get commit.", 0, 0);
481 | emacs_defun(env, rime, clear_composition, "rime-lib-clear-composition", "Clear composition.", 0, 0);
482 | emacs_defun(env, rime, process_key, "rime-lib-process-key", "Process key.", 2, 2);
483 | emacs_defun(env, rime, select_schema, "rime-lib-select-schema", "Select schema", 1, 1);
484 | emacs_defun(env, rime, get_schema_list, "rime-lib-get-schema-list", "Get schema list.", 0, 0);
485 | emacs_defun(env, rime, string_length, "rime-lib-string-length", "Get length of string", 1, 1);
486 | emacs_defun(env, rime, get_option, "rime-lib-get-option", "Get option", 1, 1);
487 | emacs_defun(env, rime, set_option, "rime-lib-set-option", "Set option", 2, 2);
488 | emacs_defun(env, rime, inline_ascii, "rime-lib-inline-ascii", "Inline ascii", 0, 0);
489 |
490 | if (ert->size < sizeof (*ert))
491 | return 1;
492 | else
493 | return 0;
494 | }
495 |
--------------------------------------------------------------------------------
/README_EN.org:
--------------------------------------------------------------------------------
1 | #+TITLE: Emacs Rime
2 |
3 | [[https://melpa.org/#/rime][file:https://melpa.org/packages/rime-badge.svg]] [[https://stable.melpa.org/#/rime][file:https://stable.melpa.org/packages/rime-badge.svg]]
4 |
5 | [[file:https://i.imgur.com/jHpk7BT.gif]]
6 |
7 | Emacs in Rime, support multiple schemas.
8 |
9 | * Part 1: Installation & How to use
10 |
11 | Check [[file:INSTALLATION_EN.org][Intallation]].
12 | * Part 2: Rime Configuration and Data
13 | #+html:
14 | #+html: toggle-folding
15 | ** Keybindings in Rime.
16 |
17 | With following configuration, you can send a serials of keybindings to Rime.
18 | Since you may want them to help you with cursor navigation, candidate pagination and selection, defined in RIME's input method schema.
19 |
20 | Currently the keybinding with Control(C-), Meta(M-) and Shift(S-) is supported.
21 |
22 | #+BEGIN_SRC emacs-lisp
23 | ;; defaults
24 | (setq rime-translate-keybindings
25 | '("C-f" "C-b" "C-n" "C-p" "C-g" "" "" "" "" "" "" ""))
26 | #+END_SRC
27 |
28 | ** Assign Rime share-data-dir and user-data-dir
29 |
30 | ~rime-share-data-dir~ is where Rime store its configuration (including schema) post installation.
31 | E.g. default path for Linux is usually =/usr/share/rime-data= ,
32 | Normally you could just use the default value, or configure the variable to use another location.
33 | E.g. for fcitx5-rime maybe this path: =~/.local/share/fcitx5/rime= .
34 |
35 | ~rime-user-data-dir~ is where emacs-rime deploy at, including vocabulary frequency information.
36 | The default path is =~/.emacs.d/rime= .
37 | Configure the variable to use another location.
38 |
39 | *Making ~emacs-rime~ 与 ~fcitx-rime~ share user-data-dir is NOT recommended* 。
40 | Take using terra-pinyin in fcitx-rime and emacs-rime as an example, if configured
41 | #+BEGIN_SRC emacs-lisp
42 | (setq rime-user-data-dir "~/.config/fcitx/rime/")
43 | #+END_SRC
44 | then after first deployment of ~emacs-rime~ , a new ~terra_pinyin.userdb/~ folder is generated, and the original ~fcitx-rime~ usage logs are be moved to ~terra_pinyin.userdb.old/~ , leading to an empty vocabulary frequency information in ~terra_pinyin.userdb.txt~ .
45 |
46 | ** Shortcut to open Rime configuration file
47 |
48 | Use ~rime-open-configuration~.
49 |
50 | ** Redeploy
51 | #+html:
52 | #+html: toggle-folding
53 |
54 | Same as RIME, the configuration file of ~emacs-rime~ does NOT take effect until redeployment.
55 |
56 | Take adding terra-pinyin as an example.
57 |
58 | Find the path where the configuration of ~emacs-rime~ locates,
59 | or use ~M-x rime-open-configuration~ to open ~default.custom.yaml~ ,
60 | goto ~patch:schema_list~ and add ~- schema: terra_pinyin~ .
61 | Now you need ~M-x rime-deploy~ to redeploy to enable terra-pinyin.
62 | After that you may press ~C-`~ to pop up rime menu and select the desired schema, i.e. terra-pinyin.
63 |
64 | Example:
65 | #+BEGIN_SRC yaml
66 | patch:
67 | schema_list:
68 | - schema: luna_pinyin
69 | - schema: pinyin_simp
70 | - schema: terra_pinyin
71 | menu/page_size: 7 # show 7 candidates per page.
72 | switcher:
73 | hotkeys:
74 | - Control+grave # Key binding to active RIME's menu. Some editions of RIME support as shortcut, which is likely to clash with other programs.
75 | #+END_SRC
76 |
77 | *** Lost user vocabulary database after redeployment?
78 |
79 | The cause may be using the same user-data-dir by ~emacs-rime~ and ~fcitx-rime~ , as explained earlier.
80 |
81 | How to recover: (still, take terra_pinyin as an example) Set user-data-dir of ~emacs-rime~ to another folder, delete ~terra_pinyin.userdb/~ and rename ~terra_pinyin.userdb.old/~ to the previous one, resync or redeploy, ~terra_pinyin.userdb.txt~ should recover now.
82 |
83 | #+html:
84 | ** Sync vocabulary
85 | #+html:
86 | #+html: toggle-folding
87 |
88 | ~M-x rime-sync~ is capable of syncing and backing up for RIME input method schema and vocabulary data, and every sync is a dual-directional sync.
89 | The backup file generated for vocabulary data is ~sync/ins_id/schema.userdb.txt~ , the file is the plain text of vocabulary data and vocabulary frequency log in the folder ~schema.userdb/~ , for convenience using it across platforms on multiple devices.
90 |
91 | The so called dual-directional sync is that, the vocabulary frequency and user customed vocabulary of current device (in ~schema.userdb/~ ) and the vocabulary logged in the backup file (in ~sync/ins_id/schema.userdb.txt~ ) will be merged by RIME, and the generated *union* will continue to be logged in ~schema.userdb/~ . Then a new backup file will be generated, still naming as ~sync/ins_id/schema.userdb.txt~ , and (without prompt to ask the user) override the old one.
92 |
93 | The mentioned path of ~sync~ folder and configuration file ~default.custom.yaml~ is under the same directory.
94 | ~ins_id~ , respectively, is the value of ~installation_id~ in the file ~installation.yaml~ . The default value is generated by random, and can be set to some customed string value.
95 |
96 | Taking adding terra-pinyin and sync as an example. After enable this input method schema, a folder ~terra_pinyin.userdb~ under RIME share data directory is generated, which has the log of usage frequency user defined vocabulary, so better not modify it.
97 | Before you sync, modify the content of ~installation.yaml~ to the customed ~installation_id: "hesperus"~ . Then ~M-x rime-sync~ , which will generate the file ~terra_pinyin.userdb.txt~ (vocabulary data) and ~terra_pinyin.schema.yaml~ (input method schema) under ~sync/hesperus/~ .
98 |
99 | If you have vocabulary data accumulated on other devices or systems, and you want to keep using it,
100 | then you should sync in the original system, and copy the generated ~terra_pinyin.userdb.txt~ to the targed system's ~sync/hesperus/~ ,
101 | then do sync or deploy.
102 | Now, the vocabulary data backed up in the old system willby merged under ~terra_pinyin.userdb/~ in the new system,
103 | and new union will be exported too, overriding ~terra_pinyin.userdb.txt~ .
104 |
105 | #+html:
106 | *** Vocabulary data sync failed?
107 | #+html:
108 | #+html: toggle-folding
109 |
110 | Take using terra-pinyin as an example.
111 |
112 | *It's suggested to set a same value for ~installation_id~ on different devices or systems.* If they're not the same, the sync process may fail, i.e. the vocabulary frequency log in ~terra_pinyin.userdb.txt~ copied from old system won't be obtained in current ~terra_pinyin.userdb/~ .
113 | At time, the vocabulary frequency info in this file is not empty, but with a different ~user_id~ , and possibly not take effect even after modify the value and resync.
114 | #+html:
115 | #+html:
116 | #+html:
117 | * Part 3: Appearance
118 | #+html:
119 | #+html: toggle-folding
120 | ** Candidate menu style
121 |
122 | Set via ~rime-show-candidate~.
123 |
124 | | Value | description |
125 | |------------+-----------------------------------------------------------------------------|
126 | | ~nil~ | don't show candidate at all. |
127 | | ~minibuffer~ | Display in minibuffer. |
128 | | ~message~ | Display with ~message~ function, useful when you use minibuffer as mode-line. |
129 | | ~popup~ | Use popup. |
130 | | ~posframe~ | Use posfarme, will fallback to popup in TUI |
131 | | ~sidewindow~ | Use sidewindow.
132 |
133 | ** Candidate style
134 |
135 | | Face | Meaning |
136 | |---------------------------+--------------------------------------------------------|
137 | | ~rime-default-face~ | default foreground and background color(posframe only) |
138 | | ~rime-code-face~ | color of code |
139 | | ~rime-candidate-num-face~ | color of candidate number |
140 | | ~rime-comment-face~ | color of candidate comment |
141 |
142 | ** posframe/popup/sidewindow candidate style
143 |
144 | Configure ~rime-posframe-style~ , ~rime-popup-style~ or ~rime-sidewindow-style~ , possible values:
145 |
146 | | value | meaning |
147 | |--------------+---------------------|
148 | | ~simple~ | single lined |
149 | | ~horizontal~ | horizontal(default) |
150 | | ~vertical~ | vertical |
151 |
152 | ** Other properties of posframe
153 |
154 | Configure ~rime-posframe-properties~ , but ~rime-default-face~ for color.
155 |
156 | #+begin_src emacs-lisp
157 | (setq rime-posframe-properties
158 | (list :font "sarasa ui sc"
159 | :internal-border-width 10))
160 | #+end_src
161 |
162 | For supported configuration, see [[https://github.com/tumashu/posframe/blob/master/posframe.el#L212][posframe]] .
163 |
164 | ** Other properties of sidewindow
165 |
166 | Configure ~rime-sidewindow-style~ , possible values are ~top~ , ~bottom~ , ~left~ , ~right~ , meaning the position where sidewindow should appear.
167 |
168 | Configure ~rime-sidewindow-keep-window~ , if ~t~ then keeps sidewindow open.
169 |
170 | ** The lighter
171 |
172 | You can get a lighter via ~(rime-lighter)~, which returns you a colored ~ㄓ~.
173 | Put it in modeline or anywhere you want.
174 |
175 | You can customize with ~rime-title~, ~rime-indicator-face~ and ~rime-indicator-dim-face~.
176 |
177 | ** The soft cursor
178 |
179 | Default to ~|~ , you can customize it with
180 |
181 | #+BEGIN_SRC emacs-lisp
182 | (setq rime-cursor "˰")
183 | #+END_SRC
184 |
185 | ** Style of preedit text
186 |
187 | Configure ~rime-preedit-face~ .
188 |
189 | ** Appearence format of preedit code
190 |
191 | Configure ~rime-show-preedit~, possible values:
192 | | values | meaning |
193 | |----------+------------------------|
194 | | ~t~ | show in menu |
195 | | ~inline~ | replace commit preview |
196 | | ~nil~ | don't show |
197 |
198 | Note: Soft cursor won't appear using ~inline~ or ~nil~ .
199 |
200 | #+html:
201 | #+html:
202 | * Part 4: Automation
203 | #+html:
204 | #+html: toggle-folding
205 | ** Temporarily ascii mode
206 |
207 | If you want specific a list of rules to automatically enable ascii mode, you can customize ~rime-disable-predicates~.
208 |
209 | Following is an example to use ascii mode in ~evil-normal-state~ or when cursor is after alphabet character or when cursor is in code.
210 |
211 | #+BEGIN_SRC emacs-lisp
212 | (setq rime-disable-predicates
213 | '(rime-predicate-evil-mode-p
214 | rime-predicate-after-alphabet-char-p
215 | rime-predicate-prog-in-code-p))
216 | #+END_SRC
217 |
218 | *** Built-in Predicate Functions
219 |
220 | - ~rime-predicate-after-alphabet-char-p~
221 |
222 | After an alphabet character (must beginning with letter [a-zA-Z]).
223 |
224 | - ~rime-predicate-after-ascii-char-p~
225 |
226 | After any alphabet character.
227 |
228 | - ~rime-predicate-prog-in-code-p~
229 |
230 | On ~prog-mode~ and ~conf-mode~, not in comments and quotes.
231 |
232 | - ~rime-predicate-in-code-string-p~
233 |
234 | In the code string(not comment string).
235 |
236 | - ~rime-predicate-evil-mode-p~
237 |
238 | In the non-editing state of ~evil-mode~.
239 |
240 | - ~rime-predicate-ace-window-p~
241 |
242 | If the ~ace-window-mode~ is activated.
243 |
244 | - ~rime-predicate-hydra-p~
245 |
246 | If a ~hydra~ keymap is activated.
247 |
248 | - ~rime-predicate-current-input-punctuation-p~
249 |
250 | When entering punctuation.
251 |
252 | - ~rime-predicate-punctuation-after-space-cc-p~
253 |
254 | When entering punctuation after a Chinese character appended with whitespaces.
255 |
256 | - ~rime-predicate-punctuation-after-ascii-p~
257 |
258 | When entering punctuation after an ascii character.
259 |
260 | - ~rime-predicate-punctuation-line-begin-p~
261 |
262 | When entering punctuation at the beginning of the line.
263 |
264 | - ~rime-predicate-space-after-ascii-p~
265 |
266 | After an ascii character appended with whitespaces.
267 |
268 | - ~rime-predicate-space-after-cc-p~
269 |
270 | After a Chinese character appended with whitespaces.
271 |
272 | - ~rime-predicate-current-uppercase-letter-p~
273 |
274 | When entering a uppercase letter.
275 |
276 | - ~rime-predicate-tex-math-or-command-p~
277 |
278 | When inside a (La)TeX math environment or entering a (La)TeX command.
279 |
280 | *** Indicator for indicating the state of temporary English state
281 |
282 | Use ~(rime-lighter)~ to generate a character ~ㄓ~ for showing.
283 | Customization is available by configuring ~rime-indicator-face~ and ~rime-indicator-dim-face~ .
284 |
285 | The configuration below could replace the icon of input method, to make it using color to indicate the current state of temporary English state.
286 |
287 | #+begin_src emacs-lisp
288 | ;;; See the default value of mode-line-mule-info as reference, which may contain somthing useful.
289 | (setq mode-line-mule-info '((:eval (rime-lighter))))
290 | #+end_src
291 |
292 | *** Temporary English input based on Rime inline ascii mode
293 |
294 | Configure ~rime-inline-predicates~ , which structure is same as ~rime-disable-predicates~ but with lower priority.
295 |
296 | The function is mainly for temporary input English text with spaces.
297 |
298 | Because of the limit of current code, if not using default ~Shift_L~ to toggle inline ascii mode in Rime configuration, shoud specify that in emacs-rime.
299 | It won't activate normally unless the configurations on both sides are the same.
300 |
301 | #+begin_src emacs-lisp
302 | ;;; support shift-l, shift-r, control-l, control-r
303 | (setq rime-inline-ascii-trigger 'shift-l)
304 | #+end_src
305 |
306 | When preedit code exists, use ~rime-inline-ascii~ to toggle mode.
307 |
308 | #+begin_src emacs-lisp
309 | (define-key rime-active-mode-map (kbd "M-j") 'rime-inline-ascii)
310 | #+end_src
311 |
312 | *** Prevent specific single characters from auto commit in inline-ascii mode
313 | #+begin_src emacs-lisp
314 | (setq rime-inline-ascii-holder ?x) ; Any single character that not trigger auto commit
315 | #+end_src
316 |
317 | *** Force enable
318 |
319 | If one of ~rime-disable-predicates~ returns t, you can still force enable the input method with ~rime-force-enable~.
320 | The effect will only last for one input behavior.
321 |
322 | You probably want to give this command a keybinding.
323 |
324 | ** Auto Commit1
325 | emacs-rime has defined the function =rime-commit1= , which automatically commit the 1st item among the candidates during input state.
326 |
327 | The function is especially useful for schema that don't rely on manually selecting desired candidate.
328 |
329 | The function is not suitable for using alone, but using within other functions defined by user to suit various personal needs.
330 | *** Example: Commit the 1st item when toggle input method.
331 | Some input method programs, e.g. fcitx5-rime, support commit the 1st item before toggle input method.
332 | Here's an example to realize the same function in emacs-rime, for explaning the usage of =rime-commit1= .
333 |
334 | Now, the basic idea is that:
335 | 1. Define a function which commits the 1st item and toggles input method.
336 | 2. Bind the defined function to some keys, and use it to alternative which ~toggle-input-method~ does.
337 |
338 | Example configuration:
339 | #+begin_src elisp
340 | (defun rime-commit1-and-toggle-input-method ()
341 | "Commit the 1st item if exists, then toggle input method."
342 | (interactive)
343 | (ignore-errors (rime-commit1))
344 | (toggle-input-method))
345 |
346 | (global-set-key (kbd "C-;") #'rime-commit1-and-toggle-input-method)
347 | #+end_src
348 | Here =ignore-errors= is for preventing =rime-commit1= triggering error because that it's not loaded until every first time you enable rime during a emacs session.
349 | After the example configuration taking effects, press =C-;= to toggle input method.
350 | *** Auto commit1 for all command unrelated to emacs-rime
351 | If there's many functions that you'd like to auto-commit1 before executing them, and you don't want to define many customed functions for each of them, you could configure a auto-commit1 for all of them.
352 |
353 | How it works:
354 | - During input state of emacs-rime, if a function unrelated with rime is executed, =rime--clear-state= is triggered by default.
355 | - If set =rime-commit1-forall= to a non-nil value, the function to be triggered will be =rime-commit1= instead.
356 |
357 | Example configuration:
358 | #+begin_src elisp
359 | (setq rime-commit1-forall t)
360 | #+end_src
361 | *** Auto commit1 for pressing ESC
362 | The for-all configuration above does not apply to pressing ESC, here we have a workaround.
363 |
364 | Why it works:
365 | During input state of emacs-rime, ESC is mapped to the function =rime--escape= , which is apparently not a function unrelated with emacs-rime.
366 |
367 | Example:
368 | Taking using with evil as an example.
369 | For making ESC acts as auto-commit1 then evil-normal-state, define a function and bind it to ESC.
370 | (notice: at least for evil, =C-[= is the same as =ESC= to emacs, so that another binding for =C-[= is NOT needed.)
371 |
372 | Example configuration:
373 | #+begin_src elisp
374 | (defun rime-commit1-and-evil-normal ()
375 | "Commit the 1st item if exists, then go to evil normal state."
376 | (interactive)
377 | (rime-commit1)
378 | (evil-normal-state))
379 | (define-key rime-active-mode-map (kbd "") 'rime-commit1-and-evil-normal)
380 | #+end_src
381 | ** Auto close input method after using minibuffer
382 |
383 | Auto close as default.
384 | Set ~rime-deactivate-when-exit-minibuffer~ to nil to cancel this behavior.
385 |
386 | ** How to integrate this with evil-escape?
387 |
388 | *The following code may have performance issue*
389 |
390 | Add the following code snippet in your configuration files, then you can use [[https://github.com/syl20bnr/evil-escape][evil-escape]]
391 | to return to normal state when having nothing in editing(no preedit overlay).
392 | #+BEGIN_SRC emacs-lisp
393 | (defun rime-evil-escape-advice (orig-fun key)
394 | "advice for `rime-input-method' to make it work together with `evil-escape'.
395 | Mainly modified from `evil-escape-pre-command-hook'"
396 | (if rime--preedit-overlay
397 | ;; if `rime--preedit-overlay' is non-nil, then we are editing something, do not abort
398 | (apply orig-fun (list key))
399 | (when (featurep 'evil-escape)
400 | (let (
401 | (fkey (elt evil-escape-key-sequence 0))
402 | (skey (elt evil-escape-key-sequence 1))
403 | )
404 | (if (or (char-equal key fkey)
405 | (and evil-escape-unordered-key-sequence
406 | (char-equal key skey)))
407 | (let ((evt (read-event nil nil evil-escape-delay)))
408 | (cond
409 | ((and (characterp evt)
410 | (or (and (char-equal key fkey) (char-equal evt skey))
411 | (and evil-escape-unordered-key-sequence
412 | (char-equal key skey) (char-equal evt fkey))))
413 | (evil-repeat-stop)
414 | (evil-normal-state))
415 | ((null evt) (apply orig-fun (list key)))
416 | (t
417 | (apply orig-fun (list key))
418 | (if (numberp evt)
419 | (apply orig-fun (list evt))
420 | (setq unread-command-events (append unread-command-events (list evt))))))
421 | )
422 | (apply orig-fun (list key)))))))
423 |
424 |
425 | (advice-add 'rime-input-method :around #'rime-evil-escape-advice)
426 | #+END_SRC
427 | #+html:
428 | * Part 5: Misc
429 | #+html:
430 | #+html: toggle-folding
431 | ** Open Rime menu
432 |
433 | Assuming you use ~C-~~ for the menu.
434 |
435 | #+begin_src yaml
436 | switcher:
437 | caption: 〔方案選單〕
438 | hotkeys:
439 | - Control+grave
440 | #+end_src
441 |
442 | You can bind this key to ~rime-mode-map~ with command ~rime-send-keybinding~.
443 |
444 | #+begin_src emacs-lisp
445 | (use-package
446 | ...
447 |
448 | :bind
449 | (:map rime-mode-map
450 | ("C-`" . 'rime-send-keybinding))
451 | ...
452 | )
453 | #+end_src
454 |
455 | #+html:
456 | * Part 6: FAQ
457 |
458 | #+html:
459 | #+html: toggle-folding
460 | ** Use in isearch
461 |
462 | emacs-rime won't work properly in isearch.
463 | Use [[https://github.com/zk-phi/phi-search][phi-search]] instead.
464 |
465 | ** The last item of the candidate box is not displayed?
466 |
467 | Few users occasionally have a issue that the last candidate word is not displayed.
468 | It can be determined that this is related to `posframe`, but the reason has not
469 | been found. A temporary solution is to append a full-width whitespace to the end of
470 | the candidate list.
471 | #+BEGIN_SRC emacs-lisp
472 | (defun +rime--posframe-display-content-a (args)
473 | "Append a full-width whitespace to the input string.
474 | This can temporarily solve the problem of `posframe` occasionally
475 | \"eating\" words."
476 | (cl-destructuring-bind (content) args
477 | (let ((newresult (if (string-blank-p content)
478 | content
479 | (concat content " "))))
480 | (list newresult))))
481 |
482 | (if (fboundp 'rime--posframe-display-content)
483 | (advice-add 'rime--posframe-display-content
484 | :filter-args
485 | #'+rime--posframe-display-content-a)
486 | (error "Function `rime--posframe-display-content' is not available."))
487 | #+END_SRC
488 |
489 |
490 | ** Want a pure emacs input method without librime?
491 |
492 | Maybe, you need [[https://github.com/tumashu/pyim][pyim]].
493 |
494 | #+html:
495 |
496 |
497 | #+html:
498 |
499 | * Part Fin: Thanks for all the contributors
500 |
501 | - [[https://github.com/Z572][Z572]]
502 | - [[https://github.com/cnsunyour][cnsunyour]]
503 | - [[https://github.com/shuxiao9058][shuxiao9058]]
504 | - [[https://github.com/lkzz][lkzz]]
505 | - [[https://github.com/wsw0108][wsw0108]]
506 | - [[https://github.com/HesperusArcher][HesperusArcher]]
507 | - [[https://github.com/longminwang][longminwang]]
508 | - [[https://github.com/chuxubank][chuxubank]]
509 | - [[https://github.com/jixiuf][jixiuf]]
510 | - [[https://github.com/cireu][cireu]]
511 | - [[https://github.com/ilupin][ilupin]]
512 | - [[https://github.com/dwuggh][dwuggh]]
513 | - [[https://github.com/zilongshanren][zilongshanren]]
514 | - [[https://github.com/zhmars][zhmars]]
515 | - [[https://github.com/syohex][syohex]]
516 | - [[https://github.com/pmeiyu][pmeiyu]]
517 | - [[https://github.com/p1uxtar][p1uxtar]]
518 | - [[https://github.com/gemone][gemone]]
519 | - [[https://github.com/casouri][casouri]]
520 | - [[https://github.com/Tubo][Tubo]]
521 | - [[https://github.com/Eason0210][Eason0210]]
522 | - [[https://github.com/wang1zhen][wang1zhen]]
523 | - [[https://github.com/shenlebantongying][shenlebantongying]]
524 | - [[https://github.com/nasyxx][nasyxx]]
525 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU General Public License is a free, copyleft license for
11 | software and other kinds of works.
12 |
13 | The licenses for most software and other practical works are designed
14 | to take away your freedom to share and change the works. By contrast,
15 | the GNU General Public License is intended to guarantee your freedom to
16 | share and change all versions of a program--to make sure it remains free
17 | software for all its users. We, the Free Software Foundation, use the
18 | GNU General Public License for most of our software; it applies also to
19 | any other work released this way by its authors. You can apply it to
20 | your programs, too.
21 |
22 | When we speak of free software, we are referring to freedom, not
23 | price. Our General Public Licenses are designed to make sure that you
24 | have the freedom to distribute copies of free software (and charge for
25 | them if you wish), that you receive source code or can get it if you
26 | want it, that you can change the software or use pieces of it in new
27 | free programs, and that you know you can do these things.
28 |
29 | To protect your rights, we need to prevent others from denying you
30 | these rights or asking you to surrender the rights. Therefore, you have
31 | certain responsibilities if you distribute copies of the software, or if
32 | you modify it: responsibilities to respect the freedom of others.
33 |
34 | For example, if you distribute copies of such a program, whether
35 | gratis or for a fee, you must pass on to the recipients the same
36 | freedoms that you received. You must make sure that they, too, receive
37 | or can get the source code. And you must show them these terms so they
38 | know their rights.
39 |
40 | Developers that use the GNU GPL protect your rights with two steps:
41 | (1) assert copyright on the software, and (2) offer you this License
42 | giving you legal permission to copy, distribute and/or modify it.
43 |
44 | For the developers' and authors' protection, the GPL clearly explains
45 | that there is no warranty for this free software. For both users' and
46 | authors' sake, the GPL requires that modified versions be marked as
47 | changed, so that their problems will not be attributed erroneously to
48 | authors of previous versions.
49 |
50 | Some devices are designed to deny users access to install or run
51 | modified versions of the software inside them, although the manufacturer
52 | can do so. This is fundamentally incompatible with the aim of
53 | protecting users' freedom to change the software. The systematic
54 | pattern of such abuse occurs in the area of products for individuals to
55 | use, which is precisely where it is most unacceptable. Therefore, we
56 | have designed this version of the GPL to prohibit the practice for those
57 | products. If such problems arise substantially in other domains, we
58 | stand ready to extend this provision to those domains in future versions
59 | of the GPL, as needed to protect the freedom of users.
60 |
61 | Finally, every program is threatened constantly by software patents.
62 | States should not allow patents to restrict development and use of
63 | software on general-purpose computers, but in those that do, we wish to
64 | avoid the special danger that patents applied to a free program could
65 | make it effectively proprietary. To prevent this, the GPL assures that
66 | patents cannot be used to render the program non-free.
67 |
68 | The precise terms and conditions for copying, distribution and
69 | modification follow.
70 |
71 | TERMS AND CONDITIONS
72 |
73 | 0. Definitions.
74 |
75 | "This License" refers to version 3 of the GNU General Public License.
76 |
77 | "Copyright" also means copyright-like laws that apply to other kinds of
78 | works, such as semiconductor masks.
79 |
80 | "The Program" refers to any copyrightable work licensed under this
81 | License. Each licensee is addressed as "you". "Licensees" and
82 | "recipients" may be individuals or organizations.
83 |
84 | To "modify" a work means to copy from or adapt all or part of the work
85 | in a fashion requiring copyright permission, other than the making of an
86 | exact copy. The resulting work is called a "modified version" of the
87 | earlier work or a work "based on" the earlier work.
88 |
89 | A "covered work" means either the unmodified Program or a work based
90 | on the Program.
91 |
92 | To "propagate" a work means to do anything with it that, without
93 | permission, would make you directly or secondarily liable for
94 | infringement under applicable copyright law, except executing it on a
95 | computer or modifying a private copy. Propagation includes copying,
96 | distribution (with or without modification), making available to the
97 | public, and in some countries other activities as well.
98 |
99 | To "convey" a work means any kind of propagation that enables other
100 | parties to make or receive copies. Mere interaction with a user through
101 | a computer network, with no transfer of a copy, is not conveying.
102 |
103 | An interactive user interface displays "Appropriate Legal Notices"
104 | to the extent that it includes a convenient and prominently visible
105 | feature that (1) displays an appropriate copyright notice, and (2)
106 | tells the user that there is no warranty for the work (except to the
107 | extent that warranties are provided), that licensees may convey the
108 | work under this License, and how to view a copy of this License. If
109 | the interface presents a list of user commands or options, such as a
110 | menu, a prominent item in the list meets this criterion.
111 |
112 | 1. Source Code.
113 |
114 | The "source code" for a work means the preferred form of the work
115 | for making modifications to it. "Object code" means any non-source
116 | form of a work.
117 |
118 | A "Standard Interface" means an interface that either is an official
119 | standard defined by a recognized standards body, or, in the case of
120 | interfaces specified for a particular programming language, one that
121 | is widely used among developers working in that language.
122 |
123 | The "System Libraries" of an executable work include anything, other
124 | than the work as a whole, that (a) is included in the normal form of
125 | packaging a Major Component, but which is not part of that Major
126 | Component, and (b) serves only to enable use of the work with that
127 | Major Component, or to implement a Standard Interface for which an
128 | implementation is available to the public in source code form. A
129 | "Major Component", in this context, means a major essential component
130 | (kernel, window system, and so on) of the specific operating system
131 | (if any) on which the executable work runs, or a compiler used to
132 | produce the work, or an object code interpreter used to run it.
133 |
134 | The "Corresponding Source" for a work in object code form means all
135 | the source code needed to generate, install, and (for an executable
136 | work) run the object code and to modify the work, including scripts to
137 | control those activities. However, it does not include the work's
138 | System Libraries, or general-purpose tools or generally available free
139 | programs which are used unmodified in performing those activities but
140 | which are not part of the work. For example, Corresponding Source
141 | includes interface definition files associated with source files for
142 | the work, and the source code for shared libraries and dynamically
143 | linked subprograms that the work is specifically designed to require,
144 | such as by intimate data communication or control flow between those
145 | subprograms and other parts of the work.
146 |
147 | The Corresponding Source need not include anything that users
148 | can regenerate automatically from other parts of the Corresponding
149 | Source.
150 |
151 | The Corresponding Source for a work in source code form is that
152 | same work.
153 |
154 | 2. Basic Permissions.
155 |
156 | All rights granted under this License are granted for the term of
157 | copyright on the Program, and are irrevocable provided the stated
158 | conditions are met. This License explicitly affirms your unlimited
159 | permission to run the unmodified Program. The output from running a
160 | covered work is covered by this License only if the output, given its
161 | content, constitutes a covered work. This License acknowledges your
162 | rights of fair use or other equivalent, as provided by copyright law.
163 |
164 | You may make, run and propagate covered works that you do not
165 | convey, without conditions so long as your license otherwise remains
166 | in force. You may convey covered works to others for the sole purpose
167 | of having them make modifications exclusively for you, or provide you
168 | with facilities for running those works, provided that you comply with
169 | the terms of this License in conveying all material for which you do
170 | not control copyright. Those thus making or running the covered works
171 | for you must do so exclusively on your behalf, under your direction
172 | and control, on terms that prohibit them from making any copies of
173 | your copyrighted material outside their relationship with you.
174 |
175 | Conveying under any other circumstances is permitted solely under
176 | the conditions stated below. Sublicensing is not allowed; section 10
177 | makes it unnecessary.
178 |
179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
180 |
181 | No covered work shall be deemed part of an effective technological
182 | measure under any applicable law fulfilling obligations under article
183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
184 | similar laws prohibiting or restricting circumvention of such
185 | measures.
186 |
187 | When you convey a covered work, you waive any legal power to forbid
188 | circumvention of technological measures to the extent such circumvention
189 | is effected by exercising rights under this License with respect to
190 | the covered work, and you disclaim any intention to limit operation or
191 | modification of the work as a means of enforcing, against the work's
192 | users, your or third parties' legal rights to forbid circumvention of
193 | technological measures.
194 |
195 | 4. Conveying Verbatim Copies.
196 |
197 | You may convey verbatim copies of the Program's source code as you
198 | receive it, in any medium, provided that you conspicuously and
199 | appropriately publish on each copy an appropriate copyright notice;
200 | keep intact all notices stating that this License and any
201 | non-permissive terms added in accord with section 7 apply to the code;
202 | keep intact all notices of the absence of any warranty; and give all
203 | recipients a copy of this License along with the Program.
204 |
205 | You may charge any price or no price for each copy that you convey,
206 | and you may offer support or warranty protection for a fee.
207 |
208 | 5. Conveying Modified Source Versions.
209 |
210 | You may convey a work based on the Program, or the modifications to
211 | produce it from the Program, in the form of source code under the
212 | terms of section 4, provided that you also meet all of these conditions:
213 |
214 | a) The work must carry prominent notices stating that you modified
215 | it, and giving a relevant date.
216 |
217 | b) The work must carry prominent notices stating that it is
218 | released under this License and any conditions added under section
219 | 7. This requirement modifies the requirement in section 4 to
220 | "keep intact all notices".
221 |
222 | c) You must license the entire work, as a whole, under this
223 | License to anyone who comes into possession of a copy. This
224 | License will therefore apply, along with any applicable section 7
225 | additional terms, to the whole of the work, and all its parts,
226 | regardless of how they are packaged. This License gives no
227 | permission to license the work in any other way, but it does not
228 | invalidate such permission if you have separately received it.
229 |
230 | d) If the work has interactive user interfaces, each must display
231 | Appropriate Legal Notices; however, if the Program has interactive
232 | interfaces that do not display Appropriate Legal Notices, your
233 | work need not make them do so.
234 |
235 | A compilation of a covered work with other separate and independent
236 | works, which are not by their nature extensions of the covered work,
237 | and which are not combined with it such as to form a larger program,
238 | in or on a volume of a storage or distribution medium, is called an
239 | "aggregate" if the compilation and its resulting copyright are not
240 | used to limit the access or legal rights of the compilation's users
241 | beyond what the individual works permit. Inclusion of a covered work
242 | in an aggregate does not cause this License to apply to the other
243 | parts of the aggregate.
244 |
245 | 6. Conveying Non-Source Forms.
246 |
247 | You may convey a covered work in object code form under the terms
248 | of sections 4 and 5, provided that you also convey the
249 | machine-readable Corresponding Source under the terms of this License,
250 | in one of these ways:
251 |
252 | a) Convey the object code in, or embodied in, a physical product
253 | (including a physical distribution medium), accompanied by the
254 | Corresponding Source fixed on a durable physical medium
255 | customarily used for software interchange.
256 |
257 | b) Convey the object code in, or embodied in, a physical product
258 | (including a physical distribution medium), accompanied by a
259 | written offer, valid for at least three years and valid for as
260 | long as you offer spare parts or customer support for that product
261 | model, to give anyone who possesses the object code either (1) a
262 | copy of the Corresponding Source for all the software in the
263 | product that is covered by this License, on a durable physical
264 | medium customarily used for software interchange, for a price no
265 | more than your reasonable cost of physically performing this
266 | conveying of source, or (2) access to copy the
267 | Corresponding Source from a network server at no charge.
268 |
269 | c) Convey individual copies of the object code with a copy of the
270 | written offer to provide the Corresponding Source. This
271 | alternative is allowed only occasionally and noncommercially, and
272 | only if you received the object code with such an offer, in accord
273 | with subsection 6b.
274 |
275 | d) Convey the object code by offering access from a designated
276 | place (gratis or for a charge), and offer equivalent access to the
277 | Corresponding Source in the same way through the same place at no
278 | further charge. You need not require recipients to copy the
279 | Corresponding Source along with the object code. If the place to
280 | copy the object code is a network server, the Corresponding Source
281 | may be on a different server (operated by you or a third party)
282 | that supports equivalent copying facilities, provided you maintain
283 | clear directions next to the object code saying where to find the
284 | Corresponding Source. Regardless of what server hosts the
285 | Corresponding Source, you remain obligated to ensure that it is
286 | available for as long as needed to satisfy these requirements.
287 |
288 | e) Convey the object code using peer-to-peer transmission, provided
289 | you inform other peers where the object code and Corresponding
290 | Source of the work are being offered to the general public at no
291 | charge under subsection 6d.
292 |
293 | A separable portion of the object code, whose source code is excluded
294 | from the Corresponding Source as a System Library, need not be
295 | included in conveying the object code work.
296 |
297 | A "User Product" is either (1) a "consumer product", which means any
298 | tangible personal property which is normally used for personal, family,
299 | or household purposes, or (2) anything designed or sold for incorporation
300 | into a dwelling. In determining whether a product is a consumer product,
301 | doubtful cases shall be resolved in favor of coverage. For a particular
302 | product received by a particular user, "normally used" refers to a
303 | typical or common use of that class of product, regardless of the status
304 | of the particular user or of the way in which the particular user
305 | actually uses, or expects or is expected to use, the product. A product
306 | is a consumer product regardless of whether the product has substantial
307 | commercial, industrial or non-consumer uses, unless such uses represent
308 | the only significant mode of use of the product.
309 |
310 | "Installation Information" for a User Product means any methods,
311 | procedures, authorization keys, or other information required to install
312 | and execute modified versions of a covered work in that User Product from
313 | a modified version of its Corresponding Source. The information must
314 | suffice to ensure that the continued functioning of the modified object
315 | code is in no case prevented or interfered with solely because
316 | modification has been made.
317 |
318 | If you convey an object code work under this section in, or with, or
319 | specifically for use in, a User Product, and the conveying occurs as
320 | part of a transaction in which the right of possession and use of the
321 | User Product is transferred to the recipient in perpetuity or for a
322 | fixed term (regardless of how the transaction is characterized), the
323 | Corresponding Source conveyed under this section must be accompanied
324 | by the Installation Information. But this requirement does not apply
325 | if neither you nor any third party retains the ability to install
326 | modified object code on the User Product (for example, the work has
327 | been installed in ROM).
328 |
329 | The requirement to provide Installation Information does not include a
330 | requirement to continue to provide support service, warranty, or updates
331 | for a work that has been modified or installed by the recipient, or for
332 | the User Product in which it has been modified or installed. Access to a
333 | network may be denied when the modification itself materially and
334 | adversely affects the operation of the network or violates the rules and
335 | protocols for communication across the network.
336 |
337 | Corresponding Source conveyed, and Installation Information provided,
338 | in accord with this section must be in a format that is publicly
339 | documented (and with an implementation available to the public in
340 | source code form), and must require no special password or key for
341 | unpacking, reading or copying.
342 |
343 | 7. Additional Terms.
344 |
345 | "Additional permissions" are terms that supplement the terms of this
346 | License by making exceptions from one or more of its conditions.
347 | Additional permissions that are applicable to the entire Program shall
348 | be treated as though they were included in this License, to the extent
349 | that they are valid under applicable law. If additional permissions
350 | apply only to part of the Program, that part may be used separately
351 | under those permissions, but the entire Program remains governed by
352 | this License without regard to the additional permissions.
353 |
354 | When you convey a copy of a covered work, you may at your option
355 | remove any additional permissions from that copy, or from any part of
356 | it. (Additional permissions may be written to require their own
357 | removal in certain cases when you modify the work.) You may place
358 | additional permissions on material, added by you to a covered work,
359 | for which you have or can give appropriate copyright permission.
360 |
361 | Notwithstanding any other provision of this License, for material you
362 | add to a covered work, you may (if authorized by the copyright holders of
363 | that material) supplement the terms of this License with terms:
364 |
365 | a) Disclaiming warranty or limiting liability differently from the
366 | terms of sections 15 and 16 of this License; or
367 |
368 | b) Requiring preservation of specified reasonable legal notices or
369 | author attributions in that material or in the Appropriate Legal
370 | Notices displayed by works containing it; or
371 |
372 | c) Prohibiting misrepresentation of the origin of that material, or
373 | requiring that modified versions of such material be marked in
374 | reasonable ways as different from the original version; or
375 |
376 | d) Limiting the use for publicity purposes of names of licensors or
377 | authors of the material; or
378 |
379 | e) Declining to grant rights under trademark law for use of some
380 | trade names, trademarks, or service marks; or
381 |
382 | f) Requiring indemnification of licensors and authors of that
383 | material by anyone who conveys the material (or modified versions of
384 | it) with contractual assumptions of liability to the recipient, for
385 | any liability that these contractual assumptions directly impose on
386 | those licensors and authors.
387 |
388 | All other non-permissive additional terms are considered "further
389 | restrictions" within the meaning of section 10. If the Program as you
390 | received it, or any part of it, contains a notice stating that it is
391 | governed by this License along with a term that is a further
392 | restriction, you may remove that term. If a license document contains
393 | a further restriction but permits relicensing or conveying under this
394 | License, you may add to a covered work material governed by the terms
395 | of that license document, provided that the further restriction does
396 | not survive such relicensing or conveying.
397 |
398 | If you add terms to a covered work in accord with this section, you
399 | must place, in the relevant source files, a statement of the
400 | additional terms that apply to those files, or a notice indicating
401 | where to find the applicable terms.
402 |
403 | Additional terms, permissive or non-permissive, may be stated in the
404 | form of a separately written license, or stated as exceptions;
405 | the above requirements apply either way.
406 |
407 | 8. Termination.
408 |
409 | You may not propagate or modify a covered work except as expressly
410 | provided under this License. Any attempt otherwise to propagate or
411 | modify it is void, and will automatically terminate your rights under
412 | this License (including any patent licenses granted under the third
413 | paragraph of section 11).
414 |
415 | However, if you cease all violation of this License, then your
416 | license from a particular copyright holder is reinstated (a)
417 | provisionally, unless and until the copyright holder explicitly and
418 | finally terminates your license, and (b) permanently, if the copyright
419 | holder fails to notify you of the violation by some reasonable means
420 | prior to 60 days after the cessation.
421 |
422 | Moreover, your license from a particular copyright holder is
423 | reinstated permanently if the copyright holder notifies you of the
424 | violation by some reasonable means, this is the first time you have
425 | received notice of violation of this License (for any work) from that
426 | copyright holder, and you cure the violation prior to 30 days after
427 | your receipt of the notice.
428 |
429 | Termination of your rights under this section does not terminate the
430 | licenses of parties who have received copies or rights from you under
431 | this License. If your rights have been terminated and not permanently
432 | reinstated, you do not qualify to receive new licenses for the same
433 | material under section 10.
434 |
435 | 9. Acceptance Not Required for Having Copies.
436 |
437 | You are not required to accept this License in order to receive or
438 | run a copy of the Program. Ancillary propagation of a covered work
439 | occurring solely as a consequence of using peer-to-peer transmission
440 | to receive a copy likewise does not require acceptance. However,
441 | nothing other than this License grants you permission to propagate or
442 | modify any covered work. These actions infringe copyright if you do
443 | not accept this License. Therefore, by modifying or propagating a
444 | covered work, you indicate your acceptance of this License to do so.
445 |
446 | 10. Automatic Licensing of Downstream Recipients.
447 |
448 | Each time you convey a covered work, the recipient automatically
449 | receives a license from the original licensors, to run, modify and
450 | propagate that work, subject to this License. You are not responsible
451 | for enforcing compliance by third parties with this License.
452 |
453 | An "entity transaction" is a transaction transferring control of an
454 | organization, or substantially all assets of one, or subdividing an
455 | organization, or merging organizations. If propagation of a covered
456 | work results from an entity transaction, each party to that
457 | transaction who receives a copy of the work also receives whatever
458 | licenses to the work the party's predecessor in interest had or could
459 | give under the previous paragraph, plus a right to possession of the
460 | Corresponding Source of the work from the predecessor in interest, if
461 | the predecessor has it or can get it with reasonable efforts.
462 |
463 | You may not impose any further restrictions on the exercise of the
464 | rights granted or affirmed under this License. For example, you may
465 | not impose a license fee, royalty, or other charge for exercise of
466 | rights granted under this License, and you may not initiate litigation
467 | (including a cross-claim or counterclaim in a lawsuit) alleging that
468 | any patent claim is infringed by making, using, selling, offering for
469 | sale, or importing the Program or any portion of it.
470 |
471 | 11. Patents.
472 |
473 | A "contributor" is a copyright holder who authorizes use under this
474 | License of the Program or a work on which the Program is based. The
475 | work thus licensed is called the contributor's "contributor version".
476 |
477 | A contributor's "essential patent claims" are all patent claims
478 | owned or controlled by the contributor, whether already acquired or
479 | hereafter acquired, that would be infringed by some manner, permitted
480 | by this License, of making, using, or selling its contributor version,
481 | but do not include claims that would be infringed only as a
482 | consequence of further modification of the contributor version. For
483 | purposes of this definition, "control" includes the right to grant
484 | patent sublicenses in a manner consistent with the requirements of
485 | this License.
486 |
487 | Each contributor grants you a non-exclusive, worldwide, royalty-free
488 | patent license under the contributor's essential patent claims, to
489 | make, use, sell, offer for sale, import and otherwise run, modify and
490 | propagate the contents of its contributor version.
491 |
492 | In the following three paragraphs, a "patent license" is any express
493 | agreement or commitment, however denominated, not to enforce a patent
494 | (such as an express permission to practice a patent or covenant not to
495 | sue for patent infringement). To "grant" such a patent license to a
496 | party means to make such an agreement or commitment not to enforce a
497 | patent against the party.
498 |
499 | If you convey a covered work, knowingly relying on a patent license,
500 | and the Corresponding Source of the work is not available for anyone
501 | to copy, free of charge and under the terms of this License, through a
502 | publicly available network server or other readily accessible means,
503 | then you must either (1) cause the Corresponding Source to be so
504 | available, or (2) arrange to deprive yourself of the benefit of the
505 | patent license for this particular work, or (3) arrange, in a manner
506 | consistent with the requirements of this License, to extend the patent
507 | license to downstream recipients. "Knowingly relying" means you have
508 | actual knowledge that, but for the patent license, your conveying the
509 | covered work in a country, or your recipient's use of the covered work
510 | in a country, would infringe one or more identifiable patents in that
511 | country that you have reason to believe are valid.
512 |
513 | If, pursuant to or in connection with a single transaction or
514 | arrangement, you convey, or propagate by procuring conveyance of, a
515 | covered work, and grant a patent license to some of the parties
516 | receiving the covered work authorizing them to use, propagate, modify
517 | or convey a specific copy of the covered work, then the patent license
518 | you grant is automatically extended to all recipients of the covered
519 | work and works based on it.
520 |
521 | A patent license is "discriminatory" if it does not include within
522 | the scope of its coverage, prohibits the exercise of, or is
523 | conditioned on the non-exercise of one or more of the rights that are
524 | specifically granted under this License. You may not convey a covered
525 | work if you are a party to an arrangement with a third party that is
526 | in the business of distributing software, under which you make payment
527 | to the third party based on the extent of your activity of conveying
528 | the work, and under which the third party grants, to any of the
529 | parties who would receive the covered work from you, a discriminatory
530 | patent license (a) in connection with copies of the covered work
531 | conveyed by you (or copies made from those copies), or (b) primarily
532 | for and in connection with specific products or compilations that
533 | contain the covered work, unless you entered into that arrangement,
534 | or that patent license was granted, prior to 28 March 2007.
535 |
536 | Nothing in this License shall be construed as excluding or limiting
537 | any implied license or other defenses to infringement that may
538 | otherwise be available to you under applicable patent law.
539 |
540 | 12. No Surrender of Others' Freedom.
541 |
542 | If conditions are imposed on you (whether by court order, agreement or
543 | otherwise) that contradict the conditions of this License, they do not
544 | excuse you from the conditions of this License. If you cannot convey a
545 | covered work so as to satisfy simultaneously your obligations under this
546 | License and any other pertinent obligations, then as a consequence you may
547 | not convey it at all. For example, if you agree to terms that obligate you
548 | to collect a royalty for further conveying from those to whom you convey
549 | the Program, the only way you could satisfy both those terms and this
550 | License would be to refrain entirely from conveying the Program.
551 |
552 | 13. Use with the GNU Affero General Public License.
553 |
554 | Notwithstanding any other provision of this License, you have
555 | permission to link or combine any covered work with a work licensed
556 | under version 3 of the GNU Affero General Public License into a single
557 | combined work, and to convey the resulting work. The terms of this
558 | License will continue to apply to the part which is the covered work,
559 | but the special requirements of the GNU Affero General Public License,
560 | section 13, concerning interaction through a network will apply to the
561 | combination as such.
562 |
563 | 14. Revised Versions of this License.
564 |
565 | The Free Software Foundation may publish revised and/or new versions of
566 | the GNU General Public License from time to time. Such new versions will
567 | be similar in spirit to the present version, but may differ in detail to
568 | address new problems or concerns.
569 |
570 | Each version is given a distinguishing version number. If the
571 | Program specifies that a certain numbered version of the GNU General
572 | Public License "or any later version" applies to it, you have the
573 | option of following the terms and conditions either of that numbered
574 | version or of any later version published by the Free Software
575 | Foundation. If the Program does not specify a version number of the
576 | GNU General Public License, you may choose any version ever published
577 | by the Free Software Foundation.
578 |
579 | If the Program specifies that a proxy can decide which future
580 | versions of the GNU General Public License can be used, that proxy's
581 | public statement of acceptance of a version permanently authorizes you
582 | to choose that version for the Program.
583 |
584 | Later license versions may give you additional or different
585 | permissions. However, no additional obligations are imposed on any
586 | author or copyright holder as a result of your choosing to follow a
587 | later version.
588 |
589 | 15. Disclaimer of Warranty.
590 |
591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599 |
600 | 16. Limitation of Liability.
601 |
602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610 | SUCH DAMAGES.
611 |
612 | 17. Interpretation of Sections 15 and 16.
613 |
614 | If the disclaimer of warranty and limitation of liability provided
615 | above cannot be given local legal effect according to their terms,
616 | reviewing courts shall apply local law that most closely approximates
617 | an absolute waiver of all civil liability in connection with the
618 | Program, unless a warranty or assumption of liability accompanies a
619 | copy of the Program in return for a fee.
620 |
621 | END OF TERMS AND CONDITIONS
622 |
623 | How to Apply These Terms to Your New Programs
624 |
625 | If you develop a new program, and you want it to be of the greatest
626 | possible use to the public, the best way to achieve this is to make it
627 | free software which everyone can redistribute and change under these terms.
628 |
629 | To do so, attach the following notices to the program. It is safest
630 | to attach them to the start of each source file to most effectively
631 | state the exclusion of warranty; and each file should have at least
632 | the "copyright" line and a pointer to where the full notice is found.
633 |
634 |
635 | Copyright (C)
636 |
637 | This program is free software: you can redistribute it and/or modify
638 | it under the terms of the GNU General Public License as published by
639 | the Free Software Foundation, either version 3 of the License, or
640 | (at your option) any later version.
641 |
642 | This program is distributed in the hope that it will be useful,
643 | but WITHOUT ANY WARRANTY; without even the implied warranty of
644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645 | GNU General Public License for more details.
646 |
647 | You should have received a copy of the GNU General Public License
648 | along with this program. If not, see .
649 |
650 | Also add information on how to contact you by electronic and paper mail.
651 |
652 | If the program does terminal interaction, make it output a short
653 | notice like this when it starts in an interactive mode:
654 |
655 | Copyright (C)
656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657 | This is free software, and you are welcome to redistribute it
658 | under certain conditions; type `show c' for details.
659 |
660 | The hypothetical commands `show w' and `show c' should show the appropriate
661 | parts of the General Public License. Of course, your program's commands
662 | might be different; for a GUI interface, you would use an "about box".
663 |
664 | You should also get your employer (if you work as a programmer) or school,
665 | if any, to sign a "copyright disclaimer" for the program, if necessary.
666 | For more information on this, and how to apply and follow the GNU GPL, see
667 | .
668 |
669 | The GNU General Public License does not permit incorporating your program
670 | into proprietary programs. If your program is a subroutine library, you
671 | may consider it more useful to permit linking proprietary applications with
672 | the library. If this is what you want to do, use the GNU Lesser General
673 | Public License instead of this License. But first, please read
674 | .
675 |
--------------------------------------------------------------------------------
/rime.el:
--------------------------------------------------------------------------------
1 | ;;; rime.el --- Rime input method -*- lexical-binding: t -*-
2 |
3 | ;; Author: Shi Tianshu
4 | ;; Keywords: convenience, input-method
5 | ;; Package-Requires: ((emacs "26.3") (dash "2.17.0") (cl-lib "0.6.1") (popup "0.5.3") (posframe "0.1.0"))
6 | ;; Version: 1.0.5
7 | ;; URL: https://www.github.com/DogLooksGood/emacs-rime
8 | ;;
9 | ;; This file is not part of GNU Emacs.
10 |
11 | ;; This program is free software; you can redistribute it and/or
12 | ;; modify it under the terms of the GNU General Public License
13 | ;; as published by the Free Software Foundation; either version 3
14 | ;; of the License, or (at your option) any later version.
15 |
16 | ;; This program is distributed in the hope that it will be useful,
17 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 | ;; GNU General Public License for more details.
20 |
21 | ;; You should have received a copy of the GNU General Public License
22 | ;; along with GNU Emacs; see the file COPYING. If not, write to the
23 | ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
24 | ;; Boston, MA 02110-1301, USA.
25 |
26 | ;;; Commentary:
27 |
28 | ;; Emacs in Rime, support multiple schemas.
29 | ;;
30 | ;; * Installation
31 | ;;
32 | ;; Note: ~make~ and ~gcc~ is required.
33 | ;;
34 | ;; ** Linux
35 | ;;
36 | ;; Install librime with your package manager, if you are using fcitx-rime or ibus-rime,
37 | ;; the librime should be already installed.
38 | ;;
39 | ;; Emacs configuration:
40 | ;;
41 | ;; #+BEGIN_SRC emacs-lisp
42 | ;; (use-package rime
43 | ;; :custom
44 | ;; (default-input-method "rime"))
45 | ;; #+END_SRC
46 | ;;
47 | ;; ** MacOS
48 | ;;
49 | ;; Download librime release.
50 | ;;
51 | ;; #+BEGIN_SRC bash
52 | ;; wget https://github.com/rime/librime/releases/download/1.7.1/rime-1.7.1-osx.zip
53 | ;; unzip rime-1.7.1-osx.zip -d ~/.emacs.d/librime
54 | ;; rm -rf rime-1.7.1-osx.zip
55 | ;; #+END_SRC
56 | ;;
57 | ;; Emacs configuration:
58 | ;;
59 | ;; #+BEGIN_SRC emacs-lisp
60 | ;; (use-package rime
61 | ;; :init
62 | ;; :custom
63 | ;; (rime-librime-root "~/.emacs.d/librime/dist")
64 | ;; (default-input-method "rime"))
65 | ;; #+END_SRC
66 | ;;
67 | ;; * Keybindings in Rime.
68 | ;;
69 | ;; With following configuration, you can send a serials of keybindings to Rime.
70 | ;; Since you may want them to help you with cursor navigation, candidate pagination and selection.
71 | ;;
72 | ;; Currently the keybinding with Control(C-), Meta(M-) and Shift(S-) is supported.
73 | ;;
74 | ;; #+BEGIN_SRC emacs-lisp
75 | ;; ;; defaults
76 | ;; (setq rime-translate-keybindings
77 | ;; '("C-f" "C-b" "C-n" "C-p" "C-g"))
78 | ;; #+END_SRC
79 | ;;
80 | ;; * Candidate menu style
81 | ;;
82 | ;; Set via ~rime-show-candidate~.
83 | ;;
84 | ;; | Value | description |
85 | ;; |--------------+-------------------------------------------------------------------------------|
86 | ;; | ~nil~ | don't show candidate at all. |
87 | ;; | ~minibuffer~ | Display in minibuffer. |
88 | ;; | ~message~ | Display with ~message~ function, useful when you use minibuffer as mode-line. |
89 | ;; | ~popup~ | Use popup. |
90 | ;; | ~posframe~ | Use posfarme, will fallback to popup in TUI |
91 | ;; | ~sidewindow~ | Use sidewindow. |
92 | ;;
93 | ;; * The lighter
94 | ;;
95 | ;; You can get a lighter via ~(rime-lighter)~, which returns you a colored ~ㄓ~.
96 | ;; Put it in modeline or anywhere you want.
97 | ;;
98 | ;; You can customize with ~rime-title~, ~rime-indicator-face~ and ~rime-indicator-dim-face~.
99 | ;;
100 | ;; * Temporarily ascii mode
101 | ;;
102 | ;; If you want specific a list of rules to automatically enable ascii mode, you can customize ~rime-disable-predicates~.
103 | ;;
104 | ;; Following is a example to use ascii mode in ~evil-normal-state~ or when cursor is after alphabet character or when cursor is in code.
105 | ;;
106 | ;; #+BEGIN_SRC emacs-lisp
107 | ;; (setq rime-disable-predicates
108 | ;; '(evil-normal-state-p
109 | ;; rime--after-alphabet-char-p
110 | ;; rime--prog-in-code-p))
111 | ;; #+END_SRC
112 | ;;
113 | ;; ** Force enable
114 | ;;
115 | ;; If one of ~rime-disable-predicates~ returns t, you can still force enable the input method with ~rime-force-enable~.
116 | ;; The effect will only last for one input behavior.
117 | ;;
118 | ;; You probably want to give this command a keybinding.
119 | ;;
120 | ;; * The soft cursor
121 | ;;
122 | ;; Default to ~|~ , you can customize it with
123 | ;;
124 | ;; #+BEGIN_SRC emacs-lisp
125 | ;; (setq rime-cursor "˰")
126 | ;; #+END_SRC
127 | ;;
128 | ;; * Shortcut to open Rime configuration file
129 | ;;
130 | ;; Use ~rime-open-configuration~.
131 |
132 |
133 | ;;; Code:
134 |
135 | (require 'seq)
136 | (require 'subr-x)
137 | (require 'dash)
138 | (require 'cl-lib)
139 | (require 'popup nil t)
140 | (require 'posframe nil t)
141 | (require 'xdg)
142 |
143 | (defconst rime-version "1.0.5")
144 |
145 | (defgroup rime nil
146 | "Custom group for emacs-rime."
147 | :group 'rime-module)
148 |
149 | (defface rime-preedit-face
150 | '((((class color) (background dark))
151 | (:inverse-video t))
152 | (((class color) (background light))
153 | (:inverse-video t)))
154 | "Face for inline preedit."
155 | :group 'rime)
156 |
157 | (defface rime-indicator-face
158 | '((((class color) (background dark))
159 | (:foreground "#9256B4" :bold t))
160 | (((class color) (background light))
161 | (:foreground "#9256B4" :bold t)))
162 | "Face for mode-line indicator when input-method is available."
163 | :group 'rime)
164 |
165 | (defface rime-indicator-dim-face
166 | '((((class color) (background dark))
167 | (:foreground "#606060" :bold t))
168 | (((class color) (background light))
169 | (:foreground "#606060" :bold t)))
170 | "Face for mode-line indicator when input-method is temporarily disabled."
171 | :group 'rime)
172 |
173 | (defcustom rime-popup-properties
174 | (list :margin 1)
175 | "Properties for popup."
176 | :type '(plist)
177 | :group 'rime)
178 |
179 | (defcustom rime-popup-style 'horizontal
180 | "Display style when using popup.
181 |
182 | `simple', preedit and candidate list in a single line.
183 | `horizontal', list candidates in a single line.'
184 | `vertical', display candidates in multiple lines."
185 | :type '(choice (const simple)
186 | (const horizontal)
187 | (const vertical))
188 | :group 'rime)
189 |
190 | (defcustom rime-posframe-properties
191 | (list :internal-border-width 10)
192 | "Properties for posframe.
193 |
194 | Background and default foreground can be set in face `rime-default-face'."
195 | :type '(plist)
196 | :group 'rime)
197 |
198 | (defcustom rime-posframe-style 'horizontal
199 | "Display style when using posframe.
200 |
201 | `simple', preedit and candidate list in a single line.
202 | `horizontal', list candidates in a single line.'
203 | `vertical', display candidates in multiple lines."
204 | :type '(choice (const simple)
205 | (const horizontal)
206 | (const vertical))
207 | :group 'rime)
208 |
209 | (defcustom rime-sidewindow-keep-window nil
210 | "Non-nil keep sidewindow open."
211 | :type 'boolean
212 | :group 'rime)
213 |
214 | (defcustom rime-sidewindow-side 'bottom
215 | "Side for sidewindow.
216 |
217 | One of `top', `bottom', `left', `right'."
218 | :type '(choice (const top)
219 | (const bottom)
220 | (const left)
221 | (const right))
222 | :group 'rime)
223 |
224 | (defcustom rime-sidewindow-style 'horizontal
225 | "Display style when using sidewindow.
226 |
227 | `simple', preedit and candidate list in a single line.
228 | `horizontal', list candidates in a single line.'
229 | `vertical', display candidates in multiple lines."
230 | :type '(choice (const simple)
231 | (const horizontal)
232 | (const vertical))
233 | :group 'rime)
234 |
235 | (defface rime-default-face
236 | '((((class color) (background dark))
237 | (:background "#333333" :foreground "#dcdccc"))
238 | (((class color) (background light))
239 | (:background "#dcdccc" :foreground "#333333")))
240 | "Face for default foreground and background."
241 | :group 'rime)
242 |
243 | (defface rime-code-face
244 | '((t (:inherit font-lock-string-face)))
245 | "Face for code in candidate, not available in `message' and `popup'."
246 | :group 'rime)
247 |
248 | (defface rime-cursor-face
249 | '((t (:inherit default)))
250 | "Face for cursor in candidate menu."
251 | :group 'rime)
252 |
253 | (defface rime-highlight-candidate-face
254 | '((t (:inherit font-lock-constant-face)))
255 | "Face for highlighted candidate."
256 | :group 'rime)
257 |
258 | (defface rime-comment-face
259 | '((t (:foreground "grey60")))
260 | "Face for comment in candidate, not available in `message' and `popup'."
261 | :group 'rime)
262 |
263 | (defface rime-candidate-num-face
264 | '((t (:inherit font-lock-comment-face)))
265 | "Face for the number before each candidate, not available in `message' and `popup'."
266 | :group 'rime)
267 |
268 | (defcustom rime-candidate-num-format-function #'rime--candidate-num-format
269 | "Function to format the number before each candidate."
270 | :type 'function
271 | :group 'rime)
272 |
273 | (defcustom rime-show-preedit t
274 | "If display preedit in candidate menu.
275 |
276 | Options:
277 | t, display in candidate menu, default behavior.
278 | inline, display in inline text, replacing commit text preview.
279 | nil, don't display."
280 | :type '(choice (const :tag "Display in candidate menu" t)
281 | (const :tag "Display in inline text" inline)
282 | (const :tag "Don't display" nil))
283 | :group 'rime)
284 |
285 | (defcustom rime-return-insert-raw t
286 | "Whether hitting return commits the raw input.
287 |
288 | If nil, hitting return commits the selected candicate instead."
289 | :type 'boolean
290 | :group 'rime)
291 |
292 | (defcustom rime-posframe-fixed-position nil
293 | "Use a fixed position for posframe candidate."
294 | :type 'boolean
295 | :group 'rime)
296 |
297 | (defcustom rime-librime-root nil
298 | "The path to the directory of librime.
299 |
300 | Leave it nil if you have librime's lib and header files in the standard path.
301 | Otherwise you should set this to where you put librime."
302 | :type 'directory
303 | :group 'rime)
304 |
305 | (defun rime--guess-emacs-module-header-root ()
306 | "Guess `emacs-module-module-header-root' from some known places."
307 | (or
308 | (let ((module-header (expand-file-name "emacs-module.h" (concat source-directory "/src/"))))
309 | (when (file-exists-p module-header)
310 | (file-name-directory module-header)))
311 | (let* ((emacs-dir (getenv "emacs_dir")) ;; https://www.gnu.org/software/emacs/manual/html_node/emacs/Misc-Variables.html
312 | (header-file (expand-file-name "emacs-module.h" (concat emacs-dir "/include/"))))
313 | (when (and emacs-dir (file-exists-p header-file))
314 | (file-name-directory header-file)))))
315 |
316 | (defcustom rime-emacs-module-header-root (rime--guess-emacs-module-header-root)
317 | "The path to the directory of Emacs module header file.
318 |
319 | Leave it nil if you using Emacs shipped with your system.
320 | Otherwise you should set this to the directory contains `emacs-module.h'."
321 | :type 'directory
322 | :group 'rime)
323 |
324 | ;;; We need these variables to be buffer local.
325 |
326 | (defvar rime--temporarily-ignore-predicates nil
327 | "Temporarily disable all predicates.
328 |
329 | Set to t will ensure the next input will be handled by input-method.
330 | Will be reset to nil when symbol `rime-active-mode' is disabled.")
331 |
332 | (defvar rime-force-enable-hook nil
333 | "Hooks run after `rime-force-enable' is called.")
334 |
335 | (defvar rime-force-enable-exit-hook nil
336 | "Hooks rum after the state of `rime-force-enable' is turned off.")
337 |
338 | (defcustom rime-deactivate-when-exit-minibuffer t
339 | "If automatically deactivate input-method when exit minibuffer."
340 | :type 'boolean
341 | :group 'rime)
342 |
343 | (defcustom rime-inline-predicates nil
344 | "A list of predicate functions, each receive no argument.
345 |
346 | When one of functions in `rime-disable-predicates' return t, and
347 | one of these functions return t, the input-method will toggle to inline mode."
348 | :type 'hook
349 | :group 'rime)
350 |
351 | (defcustom rime-disable-predicates nil
352 | "A list of predicate functions, each receive no argument.
353 |
354 | If one of these functions return t, the input-method will fallback to ascii mode."
355 | :type 'hook
356 | :group 'rime)
357 |
358 | (defcustom rime-show-candidate 'minibuffer
359 | "How we display the candidate menu.
360 |
361 | nil means don't display candidate at all.
362 | `minibuffer', display canidate in minibuffer.
363 | `popup', display with popup.el.
364 | `message', display with function `message', this is a
365 | replacement for `minibuffer' if you use minibuffer as the mode-line.
366 | `posframe', display candidate in posframe, will fallback to popup in TUI.
367 | `sidewindow', display in sidewindow."
368 | :type '(choice (const minibuffer)
369 | (const popup)
370 | (const message)
371 | (const posframe)
372 | (const sidewindow))
373 | :group 'rime)
374 |
375 | (defcustom rime-user-data-dir (locate-user-emacs-file "rime/")
376 | "Rime user data directory.
377 |
378 | Defaults to `user-emacs-directory'/rime/"
379 | :type 'directory
380 | :group 'rime)
381 |
382 | (defcustom rime-share-data-dir
383 | (cl-case system-type
384 | (gnu/linux
385 | (cl-some (lambda (parent)
386 | (let ((dir (expand-file-name "rime-data" parent)))
387 | (when (file-directory-p dir)
388 | dir)))
389 | (if (fboundp 'xdg-data-dirs)
390 | (xdg-data-dirs)
391 | '("/usr/local/share" "/usr/share"))))
392 | (darwin
393 | "/Library/Input Methods/Squirrel.app/Contents/SharedSupport")
394 | (windows-nt
395 | (if (getenv "MSYSTEM_PREFIX")
396 | (concat (getenv "MSYSTEM_PREFIX") "/share/rime-data")
397 | (if (getenv "LIBRIME_ROOT")
398 | (expand-file-name (concat (getenv "LIBRIME_ROOT") "/share/rime-data"))))))
399 | "Rime share data directory."
400 | :type 'directory
401 | :group 'rime)
402 |
403 | (defvar rime--root (file-name-directory (or load-file-name buffer-file-name))
404 | "The path to the root of rime package.")
405 |
406 | (defvar rime--module-path
407 | (concat rime--root "librime-emacs" module-file-suffix)
408 | "The path to the dynamic module.")
409 |
410 | (defcustom rime-inline-ascii-holder nil
411 | "A character that used to hold the inline ascii mode.
412 |
413 | When inline ascii is triggered, this characeter will be inserted as the
414 | beginning of composition, the origin character follows. Then this
415 | character will be deleted."
416 | :type '(choice (const nil)
417 | character)
418 | :group 'rime)
419 |
420 | (defcustom rime-inline-ascii-trigger 'shift-l
421 | "How to trigger into inline ascii mode."
422 | :type '(choice (const shift-l)
423 | (const shift-r)
424 | (const control-l)
425 | (const control-r)
426 | (const alt-l)
427 | (const alt-r))
428 | :group 'rime)
429 |
430 | (defcustom rime-cursor "|"
431 | "The character used to display the soft cursor in preedit."
432 | :type 'string
433 | :group 'rime)
434 |
435 | (defvar-local rime--preedit-overlay nil
436 | "Overlay on preedit.")
437 |
438 | (defvar rime--lib-loaded nil
439 | "If dynamic module is loaded.")
440 |
441 | (defvar rime--popup nil
442 | "The current in-use popup.")
443 |
444 | (defvar rime-posframe-buffer " *rime-posframe*"
445 | "The buffer name for candidate posframe.")
446 |
447 | (defvar rime-sidewindow-buffer " *rime-sidewindow*"
448 | "The buffer name for candidate sidewindow.")
449 |
450 | (defvar rime--hooks-for-clear-state
451 | '()
452 | "Hooks where we add function `rime--clear-state' to it.")
453 |
454 | (defvar rime--current-input-key nil
455 | "Saved last input key.")
456 |
457 | ;;;###autoload
458 | (defvar rime-title (char-to-string 12563)
459 | "The title of input method.")
460 |
461 | (defvar rime-translate-keybindings
462 | '("C-f" "C-b" "C-n" "C-p" "C-g" "" "" "" "" "" "" "")
463 | "A list of keybindings those sent to Rime during composition.
464 |
465 | Currently only Shift, Control, Meta is supported as modifiers.
466 | Each keybinding in this list, will be bound to `rime-send-keybinding' in `rime-active-mode-map'.")
467 |
468 | (defun rime--should-enable-p ()
469 | "If key event should be handled by input-method."
470 | (or rime--temporarily-ignore-predicates
471 | (not (seq-find 'funcall rime-disable-predicates))))
472 |
473 | (defun rime--should-inline-ascii-p ()
474 | "If we should toggle to inline ascii mode."
475 | (seq-find 'funcall rime-inline-predicates))
476 |
477 | (defun rime--has-composition (context)
478 | "If CONTEXT has a meaningful composition data."
479 | (not (zerop (thread-last context
480 | (alist-get 'composition)
481 | (alist-get 'length)))))
482 |
483 | (defun rime--minibuffer-display-content (content)
484 | "Display CONTENT in minibuffer."
485 | (with-selected-window (minibuffer-window)
486 | (erase-buffer)
487 | (insert content)))
488 |
489 | (defun rime--message-display-content (content)
490 | "Display CONTENT via message."
491 | (let ((message-log-max nil))
492 | (save-window-excursion
493 | (with-temp-message
494 | content
495 | (sit-for most-positive-fixnum)))))
496 |
497 | (defun rime--popup-display-content (content)
498 | "Display CONTENT with popup.el."
499 | (if (featurep 'popup)
500 | (progn
501 | (when rime--popup
502 | (popup-delete rime--popup)
503 | (setq rime--popup nil))
504 | (unless (string-blank-p content)
505 | (setq rime--popup (apply #'popup-tip content :nowait t rime-popup-properties))))
506 | ;; Fallback to popup when not available.
507 | (rime--minibuffer-display-content content)))
508 |
509 | (defun rime--minibuffer-message (string)
510 | "Concatenate STRING and minibuffer contents.
511 |
512 | Used to display in minibuffer when we are using input method in minibuffer."
513 | (message nil)
514 | (unless (string-blank-p string)
515 | (let ((inhibit-quit t)
516 | point-1)
517 | (save-excursion
518 | (insert (concat "\n" string))
519 | (setq point-1 (point)))
520 | (sit-for 1000000)
521 | (delete-region (point) point-1)
522 | (when quit-flag
523 | (setq quit-flag nil
524 | unread-command-events '(7))))))
525 |
526 | (defun rime--minibuffer-deactivate ()
527 | "Initializer for minibuffer when input method is enabled.
528 |
529 | Currently just deactivate input method."
530 | (with-selected-window (minibuffer-window)
531 | (deactivate-input-method)
532 | (remove-hook 'minibuffer-exit-hook 'rime--minibuffer-deactivate)))
533 |
534 | (defun rime--string-pixel-width (string)
535 | "Get the pixel width for STRING."
536 | (let ((window (selected-window))
537 | (remapping face-remapping-alist))
538 | (with-temp-buffer
539 | (make-local-variable 'face-remapping-alist)
540 | (setq face-remapping-alist remapping)
541 | (set-window-buffer window (current-buffer))
542 | (insert string)
543 | (let ((p (point-min))
544 | (w 0)
545 | (ft (font-at 1)))
546 | (while (< p (point-max))
547 | (setq w (+ w (or (-some-> (font-get-glyphs ft p (1+ p))
548 | (aref 0)
549 | (aref 4))
550 | 0)))
551 | (setq p (1+ p)))
552 | w))))
553 |
554 | (defun rime--posframe-display-content (content)
555 | "Display CONTENT with posframe."
556 | (if (and (featurep 'posframe) (posframe-workable-p))
557 | (if (string-blank-p content)
558 | (posframe-hide rime-posframe-buffer)
559 | (let*
560 | ((preedit (rime--current-preedit))
561 | (x (cond
562 | ((not rime-posframe-fixed-position) 0)
563 | ((not preedit) 0)
564 | ((not (overlayp rime--preedit-overlay)) 0)
565 | (t (rime--string-pixel-width preedit)))))
566 | (apply #'posframe-show rime-posframe-buffer
567 | :string content
568 | :x-pixel-offset (- x)
569 | :background-color (face-attribute 'rime-default-face :background nil t)
570 | :foreground-color (face-attribute 'rime-default-face :foreground nil t)
571 | rime-posframe-properties)))
572 | ;; Fallback to popup when not available.
573 | (rime--popup-display-content content)))
574 |
575 |
576 | (defun rime--sidewindow-display-content (content)
577 | "Display CONTENT with sidewindow."
578 | (if (fboundp 'display-buffer-in-side-window)
579 | (let* ((buffer (get-buffer-create rime-sidewindow-buffer))
580 | (window
581 | (display-buffer-in-side-window
582 | buffer
583 | `((side . ,rime-sidewindow-side)
584 | (window-height . fit-window-to-buffer)
585 | (window-weight . fit-window-to-buffer)))))
586 | (with-current-buffer buffer
587 | (when (and (string-blank-p content)
588 | (not rime-sidewindow-keep-window))
589 | (with-selected-window window
590 | (quit-window)))
591 | (erase-buffer)
592 | (insert rime-title)
593 | (insert " ")
594 | (insert content)
595 | (unless (derived-mode-p 'rime--candidate-mode)
596 | (rime--candidate-mode))))
597 | ;; Fallback to minibuffer when not available.
598 | (rime--minibuffer-display-content content)))
599 |
600 |
601 | (defun rime--show-content (content)
602 | "Display CONTENT as candidate."
603 | (if (minibufferp)
604 | (when rime-show-candidate
605 | (rime--minibuffer-message content))
606 | (cl-case rime-show-candidate
607 | (minibuffer (rime--minibuffer-display-content content))
608 | (message (rime--message-display-content content))
609 | (popup (rime--popup-display-content content))
610 | (posframe (rime--posframe-display-content content))
611 | (sidewindow (rime--sidewindow-display-content content))
612 | (t (progn)))))
613 |
614 | (defun rime--candidate-prefix-char ()
615 | "Character used to separate preedit and candidates."
616 | (if (or (and (eq 'popup rime-show-candidate)
617 | (or (eq 'horizontal rime-popup-style)
618 | (eq 'vertical rime-popup-style))
619 | (not (minibufferp)))
620 | (and (eq 'posframe rime-show-candidate)
621 | (or (eq 'horizontal rime-posframe-style)
622 | (eq 'vertical rime-posframe-style))
623 | (not (minibufferp)))
624 | (and (eq 'sidewindow rime-show-candidate)
625 | (or (eq 'horizontal rime-sidewindow-style)
626 | (eq 'vertical rime-sidewindow-style))
627 | (not (minibufferp))))
628 | "\n"
629 | " "))
630 |
631 | (defun rime--candidate-separator-char ()
632 | "Character used to spereate each candidate."
633 | (if (or (and (eq 'popup rime-show-candidate)
634 | (eq 'vertical rime-popup-style)
635 | (not (minibufferp)))
636 | (and (eq 'posframe rime-show-candidate)
637 | (eq 'vertical rime-posframe-style)
638 | (not (minibufferp)))
639 | (and (eq 'sidewindow rime-show-candidate)
640 | (eq 'vertical rime-sidewindow-style)
641 | (not (minibufferp))))
642 | "\n"
643 | " "))
644 |
645 | (defun rime--candidate-num-format (num select-labels)
646 | "Format for the number before each candidate."
647 | (if select-labels
648 | (format "%s. " (nth (1- num) select-labels))
649 | (format "%d. " num)))
650 |
651 | (defun rime--build-candidate-content ()
652 | "Build candidate menu content from librime context."
653 | (let* ((context (rime-lib-get-context))
654 | (candidates (alist-get 'candidates (alist-get 'menu context)))
655 | (composition (alist-get 'composition context))
656 | (select-labels (alist-get 'select-labels context))
657 | (preedit (alist-get 'preedit composition))
658 | (before-cursor (alist-get 'before-cursor composition))
659 | (after-cursor (alist-get 'after-cursor composition))
660 | ;; (commit-text-preview (alist-get 'commit-text-preview context))
661 | ;; (cursor-pos (alist-get 'cursor-pos composition))
662 | ;; (sel-start (alist-get 'sel-start composition))
663 | ;; (sel-end (alist-get 'sel-end composition))
664 | ;; (input (rime-lib-get-input))
665 | (menu (alist-get 'menu context))
666 | (highlighted-candidate-index (alist-get 'highlighted-candidate-index menu))
667 | (page-no (alist-get 'page-no menu))
668 | (idx 1)
669 | (result ""))
670 | (when (and (rime--has-composition context) candidates)
671 | (when (eq t rime-show-preedit)
672 | (when preedit
673 | (setq result (concat (propertize
674 | (concat before-cursor)
675 | 'face 'rime-code-face)
676 | (propertize
677 | (concat rime-cursor)
678 | 'face 'rime-cursor-face)
679 | (propertize
680 | (concat after-cursor)
681 | 'face 'rime-code-face))))
682 | (when (and page-no (not (zerop page-no)))
683 | (setq result (concat result (format " [%d]" (1+ page-no)))))
684 |
685 | (setq result (concat result (rime--candidate-prefix-char))))
686 |
687 | (dolist (c candidates)
688 | (let* ((curr (equal (1- idx) highlighted-candidate-index))
689 | (candidates-text (concat
690 | (propertize
691 | (funcall rime-candidate-num-format-function idx select-labels)
692 | 'face
693 | 'rime-candidate-num-face)
694 | (if curr
695 | (propertize (car c) 'face 'rime-highlight-candidate-face)
696 | (propertize (car c) 'face 'rime-default-face))
697 | (if-let* ((comment (cdr c)))
698 | (propertize (format " %s" comment) 'face 'rime-comment-face)
699 | ""))))
700 | (setq result (concat result
701 | candidates-text
702 | (rime--candidate-separator-char))))
703 | (setq idx (1+ idx))))
704 |
705 | result))
706 |
707 | (defun rime--show-candidate ()
708 | "Display candidate."
709 | (rime--show-content (rime--build-candidate-content)))
710 |
711 | (defun rime--parse-key-event (event)
712 | "Translate Emacs key EVENT to Rime's format.
713 |
714 | the car is keyCode, the cdr is mask."
715 | (let* ((modifiers (event-modifiers event))
716 | (type (event-basic-type event))
717 | (mask (+
718 | (if (member 'shift modifiers)
719 | 1 ; 1 << 0
720 | 0)
721 | (if (member 'meta modifiers)
722 | 8 ; 1 << 3
723 | 0)
724 | (if (member 'control modifiers)
725 | 4 ; 1 << 2
726 | 0))))
727 | (cons type mask)))
728 |
729 | (defun rime--clear-overlay ()
730 | "Clear inline preedit overlay."
731 | (when (overlayp rime--preedit-overlay)
732 | (delete-overlay rime--preedit-overlay)
733 | (setq rime--preedit-overlay nil)))
734 |
735 | (defun rime--current-preedit ()
736 | (if (eq rime-show-preedit 'inline)
737 | (thread-last (rime-lib-get-context)
738 | (alist-get 'composition)
739 | (alist-get 'preedit))
740 | (alist-get 'commit-text-preview (rime-lib-get-context))))
741 |
742 | (defun rime--display-preedit ()
743 | "Display inline preedit."
744 | (let ((preedit (rime--current-preedit)))
745 | ;; Always delete the old overlay.
746 | (rime--clear-overlay)
747 | ;; Create the new preedit
748 | (when preedit
749 | (setq rime--preedit-overlay (make-overlay (point) (point)))
750 | (overlay-put rime--preedit-overlay
751 | 'after-string
752 | (propertize
753 | preedit
754 | 'face
755 | (if (and (derived-mode-p 'org-mode 'markdown-mode)
756 | (looking-at-p "[[:print:]]"))
757 | 'rime-preedit-face
758 | (cons 'rime-preedit-face
759 | (plist-get (text-properties-at
760 | (if (> (point) (point-min))
761 | (1- (point))
762 | (point)))
763 | 'face))))))))
764 |
765 | (defun rime--rime-lib-module-ready-p ()
766 | "Return if dynamic module is loaded.
767 |
768 | If module is loaded, `rime-lib-clear-composition' should be available."
769 | (fboundp 'rime-lib-clear-composition))
770 |
771 | (defun rime--redisplay (&rest _ignores)
772 | "Display inline preedit and candidates.
773 | Optional argument IGNORES ignored."
774 | (rime--display-preedit)
775 | (rime--show-candidate))
776 |
777 | (defun rime--backspace ()
778 | "Delete one code.
779 |
780 | By default the input-method will not handle DEL, so we need this command."
781 | (interactive)
782 | (when (rime--rime-lib-module-ready-p)
783 | (let ((context (rime-lib-get-context)))
784 | (when (rime--has-composition context)
785 | (rime-lib-process-key 65288 0)
786 | (rime--redisplay)))
787 | (rime--refresh-mode-state)))
788 |
789 | (defun rime--escape ()
790 | "Clear the composition."
791 | (interactive)
792 | (when (rime--rime-lib-module-ready-p)
793 | (let ((context (rime-lib-get-context)))
794 | (when (rime--has-composition context)
795 | (rime-lib-clear-composition)
796 | (rime--redisplay)))
797 | (rime--refresh-mode-state)))
798 |
799 | (defun rime--return ()
800 | "Commit the raw input."
801 | (interactive)
802 | (when (rime--rime-lib-module-ready-p)
803 | (if rime-return-insert-raw
804 | (rime--commit
805 | (rime-lib-get-input))
806 | (rime--commit-preview))))
807 |
808 | (defun rime--shift-return ()
809 | "Commit the preedit."
810 | (interactive)
811 | (when (rime--rime-lib-module-ready-p)
812 | (rime--commit-preedit)))
813 |
814 | (defun rime--ascii-mode-p ()
815 | "If ascii-mode is enabled."
816 | (rime-lib-get-option "ascii_mode"))
817 |
818 | (defun rime--inline-ascii ()
819 | "Toggle inline ascii."
820 | (let ((key-code
821 | (cl-case rime-inline-ascii-trigger
822 | (shift-l 65505)
823 | (shift-r 65506)
824 | (control-l 65507)
825 | (control-r 65508)
826 | (alt-l 65513)
827 | (alt-r 65514))))
828 | (rime-lib-process-key key-code 0)
829 | (rime-lib-process-key key-code 1073741824)))
830 |
831 | (defun rime-inline-ascii ()
832 | "Toggle inline ascii and redisplay."
833 | (interactive)
834 | (rime--inline-ascii)
835 | (rime--redisplay))
836 |
837 | (defun rime--text-read-only-p ()
838 | "Return t if the text at point is read-only."
839 | (and (or buffer-read-only
840 | (get-pos-property (point) 'read-only))
841 | (not (or inhibit-read-only
842 | (get-pos-property (point) 'inhibit-read-only)))))
843 |
844 | (defun rime-input-method (key)
845 | "Process KEY with input method."
846 | (setq rime--current-input-key key)
847 | (when (rime--rime-lib-module-ready-p)
848 | (if (or (rime--text-read-only-p)
849 | (and (not rime-active-mode)
850 | (or (and overriding-terminal-local-map
851 | (or (not (eq (cadr overriding-terminal-local-map)
852 | universal-argument-map))
853 | (lookup-key overriding-terminal-local-map (vector key))))
854 | overriding-local-map))
855 | (and (not (rime--should-enable-p))
856 | (not (rime--has-composition (rime-lib-get-context)))))
857 | (list key)
858 | (let ((should-inline-ascii (rime--should-inline-ascii-p))
859 | (inline-ascii-prefix nil))
860 | (when (and should-inline-ascii rime-inline-ascii-holder
861 | (not (equal 32 rime--current-input-key))
862 | (string-blank-p (rime-lib-get-input)))
863 | (rime-lib-process-key rime-inline-ascii-holder 0)
864 | (rime--inline-ascii)
865 | (setq inline-ascii-prefix t))
866 | (let ((handled (rime-lib-process-key key 0)))
867 | (with-silent-modifications
868 | (let* ((context (rime-lib-get-context))
869 | (commit-text-preview (alist-get 'commit-text-preview context))
870 | ;; (preedit (thread-last context
871 | ;; (alist-get 'composition)
872 | ;; (alist-get 'preedit)))
873 | (commit (rime-lib-get-commit)))
874 | (unwind-protect
875 | (cond
876 | ((not handled)
877 | (list key))
878 | (commit
879 | (rime--clear-overlay)
880 | (mapcar 'identity commit))
881 | (t
882 | (when should-inline-ascii
883 | (if (and (not (rime--ascii-mode-p))
884 | commit-text-preview)
885 | (rime--inline-ascii)
886 | (when inline-ascii-prefix
887 | (rime-lib-set-cursor-pos 1)
888 | (rime-lib-process-key 65288 0)
889 | (rime-lib-set-cursor-pos 1))))
890 | (rime--redisplay)))
891 | (rime--refresh-mode-state)))))))))
892 |
893 | (defun rime-send-keybinding ()
894 | "Send key event to librime."
895 | (interactive)
896 | (let* ((parsed (rime--parse-key-event last-input-event))
897 | (key-raw (car parsed))
898 | (key (if (numberp key-raw)
899 | key-raw
900 | (cl-case key-raw
901 | (return #xff0d)
902 | (tab #xff09)
903 | (home #xff50)
904 | (left #xff51)
905 | (up #xff52)
906 | (right #xff53)
907 | (down #xff54)
908 | (prior #xff55)
909 | (next #xff56)
910 | (delete #xffff)
911 | (escape #xff1b)
912 | (t key-raw))))
913 | (mask (cdr parsed)))
914 | (unless (numberp key)
915 | (error "Can't send this keybinding to librime"))
916 | (rime-lib-process-key key mask)
917 | ;; check if there is something to commit only when no input available
918 | ;; since the context is not committed via input method
919 | ;; we may have some edge cases here.
920 | (when (string-blank-p (rime-lib-get-input))
921 | (-some-> (rime-lib-get-commit)
922 | (insert)))
923 | (rime--redisplay)
924 | (rime--refresh-mode-state)))
925 |
926 | (defun rime--clear-state ()
927 | "Clear composition, preedit and candidate."
928 | (setq rime--current-input-key nil)
929 | (rime-lib-clear-composition)
930 | (rime--display-preedit)
931 | (rime--show-candidate)
932 | (rime--refresh-mode-state))
933 |
934 | (defun rime--clear-state-before-unrelated-command ()
935 | "Clear state if this command is unrelated to rime."
936 | (unless (or (not (symbolp this-command))
937 | (string-prefix-p "rime-" (symbol-name this-command))
938 | (string-match-p "self-insert" (symbol-name this-command)))
939 | (rime--clear-state)))
940 |
941 | (defun rime--commit (value)
942 | "Insert VALUE, then clear state."
943 | (when (and value (rime--rime-lib-module-ready-p))
944 | (rime--clear-overlay)
945 | (insert value)
946 | (rime-lib-clear-composition)
947 | (rime--redisplay)
948 | (rime--refresh-mode-state)))
949 |
950 | (defun rime--commit-preview ()
951 | "Commit the currently previewed text."
952 | (when (rime--rime-lib-module-ready-p)
953 | (rime--commit
954 | (-some->> (rime-lib-get-context)
955 | (alist-get 'commit-text-preview)))))
956 |
957 | (defun rime--commit-preedit ()
958 | "Commit the currently previewed text."
959 | (when (rime--rime-lib-module-ready-p)
960 | (rime--commit
961 | (-some->> (rime-lib-get-context)
962 | (alist-get 'composition)
963 | (alist-get 'preedit)))))
964 |
965 | (defun rime-commit1 ()
966 | "Commit the 1st item if exists."
967 | (interactive)
968 | (when (rime-lib-process-key 32 0)
969 | (let ((commit (rime-lib-get-commit)))
970 | (insert commit)
971 | (rime--clear-state))))
972 |
973 | (defcustom rime-commit1-forall nil
974 | "Non-nil to auto commit the 1st item before any command unrelated to
975 | rime."
976 | :type 'boolean)
977 |
978 | (defun rime--commit1-before-unrelated-command ()
979 | "Commit the 1st item if this command is unrelated to rime."
980 | (unless (or (not (symbolp this-command))
981 | (string-prefix-p "rime-" (symbol-name this-command))
982 | (string-match-p "self-insert" (symbol-name this-command)))
983 | (rime-commit1)))
984 |
985 | (defun rime--refresh-mode-state ()
986 | "Toggle variable `rime-active-mode' based on if context is available."
987 | (if (rime--has-composition (rime-lib-get-context))
988 | (rime-active-mode 1)
989 | ;; Whenever we disable `rime-active-mode', we should also unset `rime--temporarily-ignore-predicates'.
990 | (when rime--temporarily-ignore-predicates
991 | (setq rime--temporarily-ignore-predicates nil)
992 | (run-hooks 'rime-force-enable-exit-hook))
993 | (rime-active-mode -1)))
994 |
995 | (defun rime-select-schema ()
996 | "Select Rime schema."
997 | (interactive)
998 | (if rime--lib-loaded
999 | (let* ((schema-list (rime-lib-get-schema-list))
1000 | (schema-names (mapcar 'cdr schema-list))
1001 | (schema-name (completing-read "Schema: " schema-names))
1002 | (schema (thread-last schema-list
1003 | (seq-find (lambda (s)
1004 | (equal (cadr s) schema-name)))
1005 | (car))))
1006 | (message "Rime schema: %s" schema-name)
1007 | (rime-lib-select-schema schema))
1008 | (message "Rime is not activated.")))
1009 |
1010 | ;;;###autoload
1011 | (defun rime-lighter ()
1012 | "Return a lighter which can be used in mode-line.
1013 |
1014 | The content is `rime-title'.
1015 |
1016 | You can customize the color with `rime-indicator-face' and `rime-indicator-dim-face'."
1017 |
1018 | (if (and (equal current-input-method "rime")
1019 | (bound-and-true-p rime-mode))
1020 | (if (and (rime--should-enable-p)
1021 | (not (rime--should-inline-ascii-p)))
1022 | (propertize
1023 | rime-title
1024 | 'face
1025 | 'rime-indicator-face)
1026 | (propertize
1027 | rime-title
1028 | 'face
1029 | 'rime-indicator-dim-face))
1030 | ""))
1031 |
1032 | (defun rime--build-compile-env ()
1033 | "Build compile env string."
1034 | (if (not module-file-suffix)
1035 | (error "Variable `module-file-suffix' is nil")
1036 | (list
1037 | (if rime-librime-root
1038 | (format "LIBRIME_ROOT=%s" (file-name-as-directory (expand-file-name rime-librime-root))))
1039 | (if rime-emacs-module-header-root
1040 | (format "EMACS_MODULE_HEADER_ROOT=%s" (shell-quote-argument
1041 | (file-name-as-directory (expand-file-name rime-emacs-module-header-root)))))
1042 | (format "MODULE_FILE_SUFFIX=%s" module-file-suffix))))
1043 |
1044 | (defun rime-compile-module ()
1045 | "Compile dynamic module."
1046 | (interactive)
1047 | (let ((env (rime--build-compile-env))
1048 | (process-environment process-environment)
1049 | (default-directory rime--root))
1050 | (cl-loop for pair in env
1051 | when pair
1052 | do (add-to-list 'process-environment pair))
1053 | (if (zerop (shell-command "make lib"))
1054 | (message "Compile succeed!")
1055 | (error "Compile Rime dynamic module failed"))))
1056 |
1057 | (defun rime--load-dynamic-module ()
1058 | "Load dynamic module."
1059 | (if (not (file-exists-p rime--module-path))
1060 | (error "Failed to compile dynamic module")
1061 | (load-file rime--module-path)
1062 | (if (rime--maybe-prompt-for-deploy)
1063 | (progn
1064 | (rime-lib-start (expand-file-name rime-share-data-dir)
1065 | (expand-file-name rime-user-data-dir))
1066 | (setq rime--lib-loaded t))
1067 | (error "Activate Rime failed"))))
1068 |
1069 | ;;;###autoload
1070 | (defun rime-activate (_name)
1071 | "Activate rime.
1072 | Argument NAME ignored."
1073 | (unless rime--lib-loaded
1074 | (unless (file-exists-p rime--module-path)
1075 | (rime-compile-module))
1076 | (rime--load-dynamic-module))
1077 |
1078 | (unless (string-equal rime-version (rime-lib-version))
1079 | (rime-compile-module)
1080 | (error "Dynamic module recompiled, please restart Emacs"))
1081 |
1082 | (when rime--lib-loaded
1083 | (dolist (binding rime-translate-keybindings)
1084 | (define-key rime-active-mode-map (kbd binding) 'rime-send-keybinding))
1085 |
1086 | (rime--clear-state)
1087 | (when (and rime-deactivate-when-exit-minibuffer (minibufferp))
1088 | (add-hook 'minibuffer-exit-hook 'rime--minibuffer-deactivate))
1089 | (dolist (hook rime--hooks-for-clear-state)
1090 | (add-hook hook 'rime--clear-state nil t))
1091 | (rime-mode 1)
1092 |
1093 | (setq-local input-method-function 'rime-input-method)
1094 | (setq-local deactivate-current-input-method-function #'rime-deactivate)))
1095 |
1096 | (defun rime-deactivate ()
1097 | "Deactivate rime."
1098 | (rime--clear-state)
1099 | (dolist (hook rime--hooks-for-clear-state)
1100 | (remove-hook hook 'rime--clear-state t))
1101 | (rime-mode -1)
1102 | (kill-local-variable 'input-method-function))
1103 |
1104 | (defvar rime-active-mode-map
1105 | (let ((keymap (make-sparse-keymap)))
1106 | (define-key keymap (kbd "DEL") 'rime--backspace)
1107 | (define-key keymap (kbd "") 'rime--backspace)
1108 | (define-key keymap (kbd "") 'rime--return)
1109 | (define-key keymap (kbd "RET") 'rime--return)
1110 | (define-key keymap (kbd "S-") 'rime--shift-return)
1111 | (define-key keymap (kbd "S-RET") 'rime--shift-return)
1112 | (define-key keymap (kbd "") 'rime--escape)
1113 | keymap)
1114 | "Keymap during composition.")
1115 |
1116 | (defvar rime-mode-map
1117 | (let ((keymap (make-sparse-keymap)))
1118 | keymap)
1119 | "Keymap when input method is enabled.")
1120 |
1121 | ;;; Initializer
1122 |
1123 | (defun rime--init-hook-default ()
1124 | "Rime activate set hooks."
1125 | (internal-push-keymap rime-active-mode-map 'overriding-terminal-local-map)
1126 | (add-hook 'post-self-insert-hook 'rime--redisplay nil t))
1127 |
1128 | (defun rime--uninit-hook-default ()
1129 | "Rime deactivate remove hooks."
1130 | (internal-pop-keymap rime-active-mode-map 'overriding-terminal-local-map)
1131 | (remove-hook 'post-self-insert-hook 'rime--redisplay t)
1132 | (rime--redisplay))
1133 |
1134 | (defun rime--init-hook-vterm ()
1135 | "Rime initialize for vterm-mode."
1136 | (advice-add 'vterm--redraw :after 'rime--redisplay)
1137 | (when (bound-and-true-p vterm-mode-map)
1138 | (define-key vterm-mode-map (kbd "") 'rime--backspace)))
1139 |
1140 | (defun rime--uninit-hook-vterm ()
1141 | "Rime finalize for vterm-mode."
1142 | (advice-remove 'vterm--redraw 'rime--redisplay)
1143 | (rime--redisplay)
1144 | (when (bound-and-true-p vterm-mode-map)
1145 | (define-key vterm-mode-map (kbd "") 'vterm-send-backspace)))
1146 |
1147 | (defun rime-active-mode--init ()
1148 | "Init for command `rime-active-mode'."
1149 | (if rime-commit1-forall
1150 | (add-hook 'pre-command-hook #'rime--commit1-before-unrelated-command t t)
1151 | (add-hook 'pre-command-hook #'rime--clear-state-before-unrelated-command t t))
1152 | (cl-case major-mode
1153 | (vterm-mode (rime--init-hook-vterm))
1154 | (t (rime--init-hook-default))))
1155 |
1156 | (defun rime-active-mode--uninit ()
1157 | "Uninit for command `rime-active-mode'."
1158 | (remove-hook 'pre-command-hook #'rime--commit1-before-unrelated-command t)
1159 | (remove-hook 'pre-command-hook #'rime--clear-state-before-unrelated-command t)
1160 | (cl-case major-mode
1161 | (vterm-mode (rime--uninit-hook-vterm))
1162 | (t (rime--uninit-hook-default))))
1163 |
1164 | (define-minor-mode rime-active-mode
1165 | "Mode used in composition.
1166 |
1167 | Should not be enabled manually."
1168 | :lighter nil
1169 | :init-value nil
1170 | :keymap nil
1171 | (if rime-active-mode
1172 | (rime-active-mode--init)
1173 | (rime-active-mode--uninit)))
1174 |
1175 | (define-minor-mode rime-mode
1176 | "Mode used when input method is activated."
1177 | :lighter nil
1178 | :init-value nil
1179 | :keymap rime-mode-map)
1180 |
1181 | ;;;###autoload
1182 | (register-input-method "rime" "euc-cn" 'rime-activate rime-title)
1183 |
1184 | (defun rime--maybe-prompt-for-deploy ()
1185 | "Prompt user to confirm the deploy action."
1186 | (let ((user-data-dir (expand-file-name rime-user-data-dir)))
1187 | (if (file-exists-p user-data-dir)
1188 | t
1189 | (yes-or-no-p
1190 | (format "Rime will use %s as the user data directory,
1191 | first time deploy could take some time. Continue?" user-data-dir)))))
1192 |
1193 | (defun rime-deploy()
1194 | "Deploy Rime."
1195 | (interactive)
1196 | (when (rime--maybe-prompt-for-deploy)
1197 | (if (not rime--lib-loaded)
1198 | (error "You should enable rime before deploy")
1199 | (rime-lib-finalize)
1200 | (rime-lib-start (expand-file-name rime-share-data-dir)
1201 | (expand-file-name rime-user-data-dir)))))
1202 |
1203 | (defun rime-sync ()
1204 | "Sync Rime user data."
1205 | (interactive)
1206 | (if (not rime--lib-loaded)
1207 | (error "You should enable rime before deploy")
1208 | (rime-lib-sync-user-data)
1209 | (rime-deploy)))
1210 |
1211 | (defun rime-force-enable ()
1212 | "Enable temporarily ascii mode.
1213 |
1214 | Will resume when finish composition."
1215 | (interactive)
1216 | (setq rime--temporarily-ignore-predicates t)
1217 | (run-hooks 'rime-force-enable-hook))
1218 |
1219 | (defun rime-open-configuration ()
1220 | "Open Rime configuration file."
1221 | (interactive)
1222 | (find-file (expand-file-name "default.custom.yaml" rime-user-data-dir)))
1223 |
1224 | (defun rime-open-schema ()
1225 | "Open Rime SCHEMA file."
1226 | (interactive)
1227 | (if rime--lib-loaded
1228 | (let* ((schema-list (rime-lib-get-schema-list))
1229 | (schema-names (mapcar 'cdr schema-list))
1230 | (schema-name (completing-read "Schema: " schema-names)))
1231 | (find-file (expand-file-name
1232 | (format "%s.custom.yaml"
1233 | (car (-find (lambda (arg) (equal (cadr arg) schema-name)) schema-list)))
1234 | rime-user-data-dir)))
1235 | (message "Rime is not activated.")))
1236 |
1237 |
1238 | (define-derived-mode rime--candidate-mode special-mode
1239 | "RC" "Mode for displaying the rime candidate."
1240 | (setq truncate-lines t
1241 | buffer-read-only nil
1242 | cursor-type nil
1243 | mode-line-format nil
1244 | tab-line-format nil)
1245 | (jit-lock-mode -1))
1246 |
1247 | (require 'rime-predicates)
1248 |
1249 | (provide 'rime)
1250 |
1251 | ;;; rime.el ends here
1252 |
--------------------------------------------------------------------------------