-
Notifications
You must be signed in to change notification settings - Fork 12
/
typo.el
471 lines (408 loc) · 17.4 KB
/
typo.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
;;; typo.el --- Minor mode for typographic editing
;; Copyright (C) 2012 Jorgen Schaefer
;; Version: 1.1
;; Author: Jorgen Schaefer <[email protected]>
;; URL: https://github.com/jorgenschaefer/typoel
;; Created: 6 Feb 2012
;; Keywords: convenience, wp
;; This program is free software; you can redistribute it and/or
;; modify it under the terms of the GNU General Public License
;; as published by the Free Software Foundation; either version 3
;; of the License, or (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program; if not, write to the Free Software
;; Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
;; 02110-1301 USA
;;; Commentary:
;; typo.el includes two modes, `typo-mode` and `typo-global-mode`.
;;
;; `typo-mode` is a buffer-specific minor mode that will change a number
;; of normal keys to make them insert typographically useful unicode
;; characters. Some of those keys can be used repeatedly to cycle through
;; variations. This includes in particular quotation marks and dashes.
;;
;; `typo-global-mode` introduces a global minor mode which adds the
;; `C-c 8` prefix to complement Emacs’ default `C-x 8` prefix map.
;;
;; See the documentation of `typo-mode` and `typo-global-mode` for
;; further details.
;;
;; ## Quotation Marks
;;
;; > “He said, ‘leave me alone,’ and closed the door.”
;;
;; All quotation marks in this sentence were added by hitting the " key
;; exactly once each. typo.el guessed the correct glyphs to use from
;; context. If it gets it wrong, you can just repeat hitting the " key
;; until you get the quotation mark you wanted.
;;
;; `M-x typo-change-language` lets you change which quotation marks to
;; use. This is also configurable, in case you want to add your own.
;;
;; ## Dashes and Dots
;;
;; The hyphen key will insert a default hyphen-minus glyph. On repeated
;; use, though, it will cycle through the en-dash, em-dash, and a number
;; of other dash-like glyphs available in Unicode. This means that typing
;; two dashes inserts an en-dash and typing three dashes inserts an
;; em-dash, as would be expected. The name of the currently inserted dash
;; is shown in the minibuffer.
;;
;; The full stop key will self-insert as usual. When three dots are
;; inserted in a row, though, they are replaced by a horizontal ellipsis
;; glyph.
;;
;; ## Other Keys
;;
;; Tick and backtick keys insert the appropriate quotation mark as well.
;; The less-than and greater-than signs cycle insert the default glyphs
;; on first use, but cycle through double and single guillemets on
;; repeated use.
;;
;; ## Prefix Map
;;
;; In addition to the above, typo-global-mode also provides a
;; globally-accessible key map under the `C-c 8` prefix (akin to Emacs’
;; default `C-x 8` prefix map) to insert various Unicode characters.
;;
;; In particular, `C-c 8 SPC` will insert a no-break space. Continued use
;; of SPC after this will cycle through half a dozen different space
;; types available in Unicode.
;;
;; Check the mode’s documentation for more details.
;;; Code:
;; For some reason, Emacs default has these as parentheses. This is
;; completely confusing when mixing this with normal parentheses,
;; and gets e.g. the following code wrong, even. Punctuation syntax
;; results in much more intuitive behavior.
(modify-syntax-entry ?» ".")
(modify-syntax-entry ?« ".")
;; Sorry for the intrusion.
(defgroup typo nil
"*Typography mode for Emacs"
:prefix "typo-"
:group 'convenience)
(defcustom typo-quotation-marks
'(("Czech" "„" "“" "‚" "‘")
("Czech (Guillemets)" "»" "«" "›" "‹")
("English" "“" "”" "‘" "’")
("German" "„" "“" "‚" "‘")
("German (Guillemets)" "»" "«" "›" "‹")
("French" "«" "»" "‹" "›")
("Finnish" "”" "”" "’" "’")
("Finnish (Guillemets)" "»" "»" "›" "›")
("Swedish" "”" "”" "’" "’")
("Russian" "«" "»" "„" "“")
("Italian" "«" "»" "“" "”")
("Polish" "„" "”" "‚" "’")
("Serbian" "„" "”" "’" "’")
("Ukrainian" "«" "»" "„" "“"))
"*Quotation marks per language."
:type '(repeat (list (string :tag "Language")
(string :tag "Double Opening Quotation Mark")
(string :tag "Double Closing Quotation Mark")
(string :tag "Single Opening Quotation Mark")
(string :tag "Single Closing Quotation Mark")))
:group 'typo)
(defcustom typo-language "English"
"*The default language typo-mode should use."
:type '(string :tag "Default Language")
:group 'typo)
(make-variable-buffer-local 'typo-language)
(put 'typo-language 'safe-local-variable 'stringp)
(defcustom typo-disable-electricity-functions '(typo-in-xml-tag)
"*A list of functions to call before an electric key binding is
used. If one of the functions returns non-nil, the key
self-inserts.
This can be used to disable the electric keys in e.g. XML tags."
:type 'hook
:options '(typo-in-xml-tag)
:group 'typo)
(defvar typo-mode-map
(let ((map (make-sparse-keymap)))
(define-key map (kbd "\"") 'typo-insert-quotation-mark)
(define-key map (kbd "'") 'typo-cycle-right-single-quotation-mark)
(define-key map (kbd "`") 'typo-cycle-left-single-quotation-mark)
(define-key map (kbd "-") 'typo-cycle-dashes)
(define-key map (kbd ".") 'typo-cycle-ellipsis)
(define-key map (kbd "<") 'typo-cycle-left-angle-brackets)
(define-key map (kbd ">") 'typo-cycle-right-angle-brackets)
map)
"The keymap for `typo-mode'.")
(defvar typo-global-mode-map
(let ((gmap (make-sparse-keymap))
(map (make-sparse-keymap)))
(define-key gmap (kbd "C-c 8") map)
(define-key map (kbd "\"") 'typo-insert-quotation-mark)
(define-key map (kbd "'") 'typo-cycle-right-single-quotation-mark)
(define-key map (kbd "`") 'typo-cycle-left-single-quotation-mark)
(define-key map (kbd "--") 'typo-cycle-dashes)
(define-key map (kbd ".") 'typo-cycle-ellipsis)
(define-key map (kbd "<<") 'typo-cycle-left-angle-brackets)
(define-key map (kbd ">>") 'typo-cycle-right-angle-brackets)
(define-key map (kbd "*") 'typo-cycle-multiplication-signs)
(define-key map (kbd "SPC") 'typo-cycle-spaces)
(define-key map (kbd "?") 'typo-cycle-question-mark)
(define-key map (kbd "!") 'typo-cycle-exclamation-mark)
(define-key map (kbd "/=") "≠")
(define-key map (kbd "//") "÷")
(define-key map (kbd ">=") "≥")
(define-key map (kbd "<=") "≤")
(define-key map (kbd "=<") "⇐")
(define-key map (kbd "=>") "⇒")
(define-key map (kbd "<-") "←")
(define-key map (kbd "-<") "←")
(define-key map (kbd "->") "→")
(define-key map (kbd "-^") "↑")
(define-key map (kbd "=^") "⇑")
(define-key map (kbd "-v") "↓")
(define-key map (kbd "=v") "⇓")
(define-key map (kbd "T") "™")
gmap)
"The keymap for `typo-global-mode'.")
;;;###autoload
(define-minor-mode typo-mode
"Minor mode for typographic editing.
This mode changes some default keybindings to enter typographic
glyphs. In particular, this changes how quotation marks, the
dash, the dot, and the angle brackets work.
Most keys will cycle through various options when used
repeatedly.
\\{typo-mode-map}"
:group 'typo
:lighter " Typo"
:keymap typo-mode-map)
;;;###autoload
(define-minor-mode typo-global-mode
"Minor mode for typographic editing.
This mode provides a prefix map under C-c 8 which complements the
default C-x 8 prefix map.
\\{typo-global-mode-map}"
:group 'typo
:global t
:keymap typo-global-mode-map)
(defun typo-change-language (language)
"Change the current language used for quotation marks."
(interactive (list (completing-read
"Quotation marks: "
typo-quotation-marks
)))
(when (not (assoc-string language typo-quotation-marks))
(error "Unknown language %s (see `typo-quotation-marks')" language))
(setq typo-language language))
(defun typo-open-double-quotation-mark ()
"Return the opening double quotation marks for the current language."
(nth 1 (assoc-string typo-language typo-quotation-marks)))
(defun typo-close-double-quotation-mark ()
"Return the closing double quotation marks for the current language."
(nth 2 (assoc-string typo-language typo-quotation-marks)))
(defun typo-open-single-quotation-mark ()
"Return the opening single quotation marks for the current language."
(nth 3 (assoc-string typo-language typo-quotation-marks)))
(defun typo-close-single-quotation-mark ()
"Return the closing single quotation marks for the current language."
(nth 4 (assoc-string typo-language typo-quotation-marks)))
(defun typo-in-xml-tag ()
"Return non-nil if point is inside an XML tag."
(save-excursion
(and (re-search-backward "[<>]"
;; If you have an XML tag that spans more
;; than 25 lines, you should be shot.
(max (point-min)
(- (point)
(* 80 25)))
t)
;; < without a word char is a math formula
(looking-at "<\\w"))))
(defun typo-electricity-disabled-p ()
"Return non-nil if electricity is disabled at point.
See `typo-disable-electricity-functions'."
;; Only if this happened from a non-prefix variable
(and (= (length (this-single-command-keys)) 1)
(run-hook-with-args-until-success 'typo-disable-electricity-functions)))
(defun typo-quotation-needs-closing (open close)
"Return non-nil if the last occurrence of either OPEN and CLOSE
in the current buffer is OPEN, i.e. if this pair still needs
closing.
This does not support nested, equal quotation marks."
(save-excursion
(if (re-search-backward (regexp-opt (list open close))
nil t)
(equal open (match-string 0))
nil)))
(defun typo-insert-quotation-mark (arg)
"Insert quotation marks.
This command tries to be intelligent. Opening quotation marks are
closed. If you repeat the command after a quotation mark, that
mark is cycled through various variants.
After a closing double quotation mark, the next variant is an
opening single quotation mark. So when this command is issued
inside a quotation, it will first close the quotation. On the
second time, it will open an inner quotation.
After an opening double quotation mark, the next variant is the
typewriter quotation mark, making it possible in the usual case
to simple issue this command twice to get a typewriter quotation
mark (use C-q \" or C-o \" to force inserting one).
If used with a numeric prefix argument, only typewriter quotation
marks will be inserted."
(interactive "P")
(if (or (typo-electricity-disabled-p) arg)
(call-interactively 'self-insert-command)
(let* ((double-open (typo-open-double-quotation-mark))
(double-close (typo-close-double-quotation-mark))
(double-needs-closing (typo-quotation-needs-closing
double-open double-close))
(single-open (typo-open-single-quotation-mark))
(single-close (typo-close-single-quotation-mark))
(single-needs-closing (typo-quotation-needs-closing
single-open single-close))
(after-any-opening (looking-back (regexp-opt (list double-open
single-open)))))
(cond
;; For languages that use the same symbol for opening and
;; closing (Finnish, Swedish...), the simplest thing to do is to
;; not try to be too smart and just cycle ” and "
((equal double-open double-close)
(typo-insert-cycle (list double-open "\"")))
;; Inside a single quotation, if we're not directly at the
;; opening one, we close it.
((and single-needs-closing
(not after-any-opening))
(insert single-close))
;; Inside a double quotation, if we're not directly at the
;; opening one ...
((and double-needs-closing
(not after-any-opening))
;; ... if we are after a space, we open an inner quotation.
;;
;; (This misses the situation where we start a quotation with an
;; inner quotation, but that's indistinguishable from cycling
;; through keys, and the latter is more common.)
(if (looking-back "\\s-")
(insert single-open)
;; Otherwise, close the double one
(insert double-close)))
;; Nothing is open, or we are directly at an opening quote. If
;; this is a repetition of a this command, start cycling.
((eq this-command last-command)
(delete-char -1)
(typo-insert-cycle (list "\""
double-open double-close
single-open single-close)))
;; Otherwise, just open a double quotation mark.
;;
;; This can actually happen if we open a quotation, then move
;; point, then go back to directly after the quotation, and then
;; call this again. Opening another double quotation there is
;; weird, but I'm not sure what else to do then, either.
(t
(insert double-open))))))
(defun typo-cycle-ellipsis (arg)
"Add periods. The third period will add an ellipsis.
If used with a numeric prefix argument N, N periods will be inserted."
(interactive "P")
(if (or (typo-electricity-disabled-p) arg)
(call-interactively 'self-insert-command)
(if (looking-back "\\.\\.")
(replace-match "…")
(call-interactively 'self-insert-command))))
(defmacro define-typo-cycle (name docstring cycle)
"Define a typo command that cycles through various options.
If used with a numeric prefix argument N, N standard characters will be
inserted instead of cycling.
NAME is the name of the command to define.
DOCSTRING is the docstring for that command.
CYCLE is a list of strings to cycle through."
(declare (indent 1) (doc-string 2))
`(defun ,name (arg)
,docstring
(interactive "P")
(if (or (typo-electricity-disabled-p) arg)
(call-interactively 'self-insert-command)
(typo-insert-cycle ',cycle))))
;; This event cycling loop is from `kmacro-call-macro'
(defun typo-insert-cycle (cycle)
"Insert the strings in CYCLE"
(let ((i 0)
(repeat-key last-input-event)
repeat-key-str)
(insert (nth i cycle))
(setq repeat-key-str (format-kbd-macro (vector repeat-key) nil))
(while repeat-key
(message "(Inserted %s; type %s for other options)"
(typo-char-name (nth i cycle))
repeat-key-str)
(if (equal repeat-key (read-event))
(progn
(clear-this-command-keys t)
(delete-char (- (length (nth i cycle))))
(setq i (% (+ i 1)
(length cycle)))
(insert (nth i cycle))
(setq last-input-event nil))
(setq repeat-key nil)))
(when last-input-event
(clear-this-command-keys t)
(setq unread-command-events (list last-input-event)))))
(defun typo-char-name (string)
"Return the Unicode name of the first char in STRING."
(let ((char-code (elt string 0))
name)
(setq name (get-char-code-property char-code 'name))
(when (or (not name)
(= ?< (elt name 0)))
(setq name (get-char-code-property char-code 'old-name)))
name))
(define-typo-cycle typo-cycle-right-single-quotation-mark
"Cycle through the right quotation mark and the typewriter apostrophe.
If used with a numeric prefix argument N, N typewriter apostrophes
will be inserted."
("’" "'"))
(define-typo-cycle typo-cycle-left-single-quotation-mark
"Cycle through the left single quotation mark and the backtick.
If used with a numeric prefix argument N, N backticks will be inserted."
("‘" "`"))
(define-typo-cycle typo-cycle-dashes
"Cycle through various dashes."
("-" ; HYPHEN-MINUS
"–" ; EN DASH
"—" ; EM DASH
"−" ; MINUS SIGN
"‐" ; HYPHEN
"‑" ; NON-BREAKING HYPHEN
))
(define-typo-cycle typo-cycle-left-angle-brackets
"Cycle through the less-than sign and guillemet quotation marks.
If used with a numeric prefix argument N, N less-than signs will be inserted."
("<" "«" "‹"))
(define-typo-cycle typo-cycle-right-angle-brackets
"Cycle through the greater-than sign and guillemet quotation marks.
If used with a numeric prefix argument N, N greater-than signs will be inserted."
(">" "»" "›"))
(define-typo-cycle typo-cycle-multiplication-signs
"Cycle through the asterisk and various multiplication signs"
("×" "·"))
(define-typo-cycle typo-cycle-spaces
"Cycle through various spaces"
(" " ; NO-BREAK SPACE
" " ; THIN SPACE
"" ; ZERO WIDTH NON-JOINER
"" ; ZERO WIDTH JOINER
" " ; MEDIUM MATHEMATICAL SPACE
" " ; HAIR SPACE
;; " " ; EM SPACE
;; " " ; EN SPACE
" " ; SPACE
))
(define-typo-cycle typo-cycle-question-mark
"Cycle through various interrogatory marks."
("?" "¿" "‽" "⸘" "⸮"))
(define-typo-cycle typo-cycle-exclamation-mark
"Cycle through various exclamatory marks."
("!" "¡" "‽" "⸘"))
(provide 'typo)
;;; typo.el ends here