emacs-hangul-input.org (10313B)
1 #+title: Emacs Hangul Input 2 #+date: <2020-07-18 Sat> 3 #+property: header-args :results none 4 5 Note: org-mode help links don't work when exported to html; [[https://git.jamzattack.xyz/yeet-log][best 6 viewed in Emacs]]. 7 8 * Keyboard layout 9 10 The variable [[help:quail-keyboard-layout-alist][quail-keyboard-layout-alist]] by default contains six 11 layouts: all qwerty. 12 13 First, we need to define and enable a dvorak layout: 14 #+name: dvorak-layout 15 #+begin_src emacs-lisp 16 (with-eval-after-load 'quail 17 (push 18 (cons "dvorak" 19 (concat 20 " " 21 "`~1!2@3#4$5%6^7&8*9(0)[{]} " ; numbers 22 " '\",<.>pPyYfFgGcCrRlL/?=+\\| " ; qwerty 23 " aAoOeEuUiIdDhHtTnNsS-_ " ; asdf 24 " ;:qQjJkKxXbBmMwWvVzZ " ; zxcv 25 " ")) 26 quail-keyboard-layout-alist) 27 28 (quail-set-keyboard-layout "dvorak")) 29 #+end_src 30 31 However, the Hangul input methods don't respect this variable, so we 32 need to hack about a bit. 33 34 * Input methods in Emacs 35 36 The way Emacs changes the input method is by defining a local 37 variable, [[help:input-method-function][input-method-function]]. 38 39 For example, if you change the layout with =C-u C-\ korean-hangul RET= 40 or ~(set-input-method 'korean-hangul)~, the above variable will be set 41 to [[help:hangul2-input-method][hangul2-input-method]]. 42 43 Because of this structure, one can simply write a function that takes 44 a single argument (the key sequence) and inserts characters 45 accordingly. Technically, an input method doesn't even need to insert 46 anything; it could just be a bunch of random commands -- such is the 47 power of Emacs. 48 49 * =hangul.el= 50 51 The Hangul library is very qwerty-based. I didn't think this would 52 pose many problems however, as that's what changing the [[*Keyboard layout][keyboard 53 layout]] should do, right? 54 55 I use the standard "korean-hangul" (AKA Dubeolsik, 두벌식) input 56 method. Others are also defined: 57 58 - Hangul 2-Bulsik input method 59 - Hangul 3-Bulsik final input method 60 - Hangul 3-Bulsik 390 input method 61 62 Side note: Hangul is known for being a very sensible and logical 63 alphabet. The keyboard layout is the same -- vowels are on the right, 64 and consonants on the left. [[https://en.wikipedia.org/wiki/Korean_language_and_computers][Wikipedia]] 65 66 ** Messing with the internal function 67 68 The function [[help:hangul2-input-method-internal][hangul2-input-method-internal]] (or its caller, without 69 =-internal=) are where the translation seemed to be missing: 70 #+name: old-internal 71 #+begin_src emacs-lisp 72 ;; Defined in lisp/leim/quail/hangul.el 73 (defun hangul2-input-method-internal (key) 74 (let ((char (+ (aref hangul2-keymap (1- (% key 32))) 75 (cond ((or (= key ?O) (= key ?P)) 2) 76 ((or (= key ?E) (= key ?Q) (= key ?R) 77 (= key ?T) (= key ?W)) 1) 78 (t 0))))) 79 (if (< char 31) 80 (hangul2-input-method-jaum char) 81 (hangul2-input-method-moum char)))) 82 #+end_src 83 84 I figured it might work fine if I just lexically rebound ~KEY~ to the 85 translation: 86 #+name: new-function 87 #+begin_src emacs-lisp 88 (defun hangul2-input-method-internal (key) 89 (setq key (quail-keyboard-translate key)) 90 (let ((char (+ (aref hangul2-keymap (1- (% key 32))) 91 (cond ((or (= key ?O) (= key ?P)) 2) 92 ((or (= key ?E) (= key ?Q) (= key ?R) 93 (= key ?T) (= key ?W)) 1) 94 (t 0))))) 95 (if (< char 31) 96 (hangul2-input-method-jaum char) 97 (hangul2-input-method-moum char)))) 98 #+end_src 99 100 *** Results 101 102 Unfortunately, this is janky as hell. There are multiple issues: 103 104 1. Some keys result in "Args out of range: [17 48 26 23 ...]". 105 2. The translation is only used for some keys. 106 107 This is because the key is checked first in [[help:hangul2-input-method][hangul2-input-method]] to 108 determine whether it is alphabetic and should be handed off to the 109 internal version. 110 111 ** hangul2-input-method 112 113 So that first one didn't work... What if I used [[help:hangul2-input-method][hangul2-input-method]] 114 instead? 115 #+name: first-try 116 #+begin_src emacs-lisp 117 ;; Defined in lisp/leim/quail/hangul.el 118 (defun hangul2-input-method (key) 119 "2-Bulsik input method." 120 (if (or buffer-read-only (not (alphabetp key))) 121 (list key) 122 (quail-setup-overlays nil) 123 (let ((input-method-function nil) 124 (echo-keystrokes 0) 125 (help-char nil)) 126 (setq hangul-queue (make-vector 6 0)) 127 (hangul2-input-method-internal key) 128 (unwind-protect 129 (catch 'exit-input-loop 130 (while t 131 (let* ((seq (read-key-sequence nil)) 132 (cmd (lookup-key hangul-im-keymap seq)) 133 key) 134 (cond ((and (stringp seq) 135 (= 1 (length seq)) 136 (setq key (aref seq 0)) 137 (alphabetp key)) 138 (hangul2-input-method-internal key)) 139 ((commandp cmd) 140 (call-interactively cmd)) 141 (t 142 (setq unread-command-events 143 (nconc (listify-key-sequence seq) 144 unread-command-events)) 145 (throw 'exit-input-loop nil)))))) 146 (quail-delete-overlays))))) 147 #+end_src 148 149 I did the same thing as above, just redefining KEY at the beginning 150 with: ~(setq key (quail-keyboard-translate key))~ 151 152 *Yes!! It works!* 153 154 아이고... There is still a major problem: only the first character is 155 actually translated through [[help:quail-input-method][quail-input-method]]. 156 157 *** The problem 158 159 You see the second half of that function? A loop that reads input 160 until a complete syllabic block has been entered. 161 162 #+name: while-loop 163 #+begin_src emacs-lisp 164 ;; From `hangul2-input-method', paraphrased 165 (let* ((seq (read-key-sequence nil)) 166 key) 167 (cond ((and (stringp seq) 168 (= 1 (length seq)) 169 (setq key (aref seq 0)) 170 (alphabetp key)) 171 (hangul2-input-method-internal key)))) 172 #+end_src 173 174 Well, I replaced the initial key, but the following keys also need to 175 be translated. This is simple -- because it's only passed to the 176 internal function when it's in the latin alphabet, it won't interfere 177 with any keybindings. 178 179 * Finale 180 181 The only changes needed to allow Hangul input with dvorak are: 182 183 - Create and/or enable a dvorak =quail-input-method= 184 - Use quail's key translations in =hangul2-input-method= 185 - For the initial key (argument of the function) 186 - For the following keys (in the while loop) 187 188 Note: This needs to be redefined after loading =quail/hangul=, so you 189 might consider wrapping it in =with-eval-after-load "quail/hangul"= in 190 your init file. 191 192 ** hangul2 193 194 #+name: final 195 #+begin_src emacs-lisp 196 (defun hangul2-input-method (key) 197 "2-Bulsik input method." 198 (setq key (quail-keyboard-translate key)) 199 (if (or buffer-read-only (not (alphabetp key))) 200 (list key) 201 (quail-setup-overlays nil) 202 (let ((input-method-function nil) 203 (echo-keystrokes 0) 204 (help-char nil)) 205 (setq hangul-queue (make-vector 6 0)) 206 (hangul2-input-method-internal key) 207 (unwind-protect 208 (catch 'exit-input-loop 209 (while t 210 (let* ((seq (read-key-sequence nil)) 211 (cmd (lookup-key hangul-im-keymap seq)) 212 key) 213 (cond 214 ((and (stringp seq) 215 (= 1 (length seq)) 216 (setq key (quail-keyboard-translate (aref seq 0))) 217 (alphabetp key)) 218 (hangul2-input-method-internal key)) 219 ((commandp cmd) 220 (call-interactively cmd)) 221 (t 222 (setq unread-command-events 223 (nconc (listify-key-sequence seq) 224 unread-command-events)) 225 (throw 'exit-input-loop nil)))))) 226 (quail-delete-overlays))))) 227 #+end_src 228 229 ** hangul3 230 231 #+begin_src emacs-lisp 232 (defun hangul3-input-method (key) 233 "3-Bulsik final input method." 234 (setq key (quail-keyboard-translate key)) 235 (if (or buffer-read-only (< key 33) (>= key 127)) 236 (list key) 237 (quail-setup-overlays nil) 238 (let ((input-method-function nil) 239 (echo-keystrokes 0) 240 (help-char nil)) 241 (setq hangul-queue (make-vector 6 0)) 242 (hangul3-input-method-internal key) 243 (unwind-protect 244 (catch 'exit-input-loop 245 (while t 246 (let* ((seq (read-key-sequence nil)) 247 (cmd (lookup-key hangul-im-keymap seq)) 248 key) 249 (cond ((and (stringp seq) 250 (= 1 (length seq)) 251 (setq key (quail-keyboard-translate (aref seq 0))) 252 (and (>= key 33) (< key 127))) 253 (hangul3-input-method-internal key)) 254 ((commandp cmd) 255 (call-interactively cmd)) 256 (t 257 (setq unread-command-events 258 (nconc (listify-key-sequence seq) 259 unread-command-events)) 260 (throw 'exit-input-loop nil)))))) 261 (quail-delete-overlays))))) 262 #+end_src 263 264 ** hangul390 265 266 #+begin_src emacs-lisp 267 (defun hangul390-input-method (key) 268 "3-Bulsik 390 input method." 269 (setq key (quail-keyboard-translate key)) 270 (if (or buffer-read-only (< key 33) (>= key 127)) 271 (list key) 272 (quail-setup-overlays nil) 273 (let ((input-method-function nil) 274 (echo-keystrokes 0) 275 (help-char nil)) 276 (setq hangul-queue (make-vector 6 0)) 277 (hangul390-input-method-internal key) 278 (unwind-protect 279 (catch 'exit-input-loop 280 (while t 281 (let* ((seq (read-key-sequence nil)) 282 (cmd (lookup-key hangul-im-keymap seq)) 283 key) 284 (cond ((and (stringp seq) 285 (= 1 (length seq)) 286 (setq key (quail-keyboard-translate (aref seq 0))) 287 (and (>= key 33) (< key 127))) 288 (hangul390-input-method-internal key)) 289 ((commandp cmd) 290 (call-interactively cmd)) 291 (t 292 (setq unread-command-events 293 (nconc (listify-key-sequence seq) 294 unread-command-events)) 295 (throw 'exit-input-loop nil)))))) 296 (quail-delete-overlays))))) 297 #+end_src