|
| 1 | +#+TITLE: TIL: 数字小键盘的小数点陷阱与行内算术求值 |
| 2 | +#+AUTHOR: lujun9972, Claude Code |
| 3 | +#+TAGS: TIL Emacs 键盘配置 |
| 4 | +#+DATE: [2026-04-27 Sun] |
| 5 | +#+LANGUAGE: zh-CN |
| 6 | +#+OPTIONS: H:6 num:nil toc:t \n:nil ::t |:t ^:nil -:nil f:t *:t <:nil |
| 7 | + |
| 8 | +Marcin Borkowski 的一篇文章[fn:1]揭示了一个容易忽略的 Emacs 键盘行为:数字小键盘右下角那个用来分隔整数和小数部分的键——在 Emacs 里被硬编码为逗号。 |
| 9 | + |
| 10 | +*注意:这个问题只影响欧洲键盘布局(波兰、德国、法国等)。这些布局下,该键发送的是 =kp-separator= 按键符号;而美式和中文键盘布局下,该键直接发送 =.= 字符,不受此问题影响。* |
| 11 | + |
| 12 | +* 问题:数字小键盘打不出小数点 |
| 13 | + |
| 14 | +受影响的用户在数字小键盘上按那个键想输入小数点,结果得到的是逗号。用 =C-h c= 查看这个键的 Emacs 名称: |
| 15 | + |
| 16 | +#+begin_example |
| 17 | +, 'COMMA' (translated from <kp-separator>) runs the command self-insert-command |
| 18 | +#+end_example |
| 19 | + |
| 20 | +关键在 =<kp-separator>= 这个名字。Emacs 在 =simple.el= 里把 =kp-separator= 硬编码映射为逗号: |
| 21 | + |
| 22 | +#+begin_src emacs-lisp |
| 23 | +(mapc |
| 24 | + (lambda (keypad-normal) |
| 25 | + (let ((keypad (nth 0 keypad-normal)) |
| 26 | + (normal (nth 1 keypad-normal))) |
| 27 | + (put keypad 'ascii-character normal) |
| 28 | + (define-key function-key-map (vector keypad) (vector normal)))) |
| 29 | + '((kp-space ?\s) |
| 30 | + (kp-tab ?\t) |
| 31 | + (kp-enter ?\r) |
| 32 | + (kp-separator ?,) ;; ← 这里 |
| 33 | + (kp-equal ?=) |
| 34 | + ...)) |
| 35 | +#+end_src |
| 36 | + |
| 37 | +这个映射和你的系统语言设置无关——不管你用的是中文、英文还是波兰文环境, =kp-separator= 一律变成逗号。修复方法是在配置中覆盖这个映射: |
| 38 | + |
| 39 | +#+begin_src emacs-lisp |
| 40 | +(keymap-set function-key-map "<kp-separator>" ".") |
| 41 | +#+end_src |
| 42 | + |
| 43 | +=function-key-map= 的机制是:按键先经过这个映射表转换,再交给后续的 keymap 处理。所以这一行配置的效果是,在所有模式中都把数字小键盘的分隔键变成小数点,而不是逗号。 |
| 44 | + |
| 45 | +* 延伸:行内算术求值 |
| 46 | + |
| 47 | +原文还提到了一个实用技巧:在任意 buffer 中将光标左侧的算术表达式直接替换为计算结果。核心逻辑很简洁: |
| 48 | + |
| 49 | +#+begin_src emacs-lisp |
| 50 | +(defun fast-calc (currency) |
| 51 | + "将光标左侧的算术表达式替换为计算结果。 |
| 52 | +带前缀参数时,保留两位小数并追加货币后缀。" |
| 53 | + (interactive "P") |
| 54 | + (when (looking-back "[-+/*().0-9]\\{2,\\} *" |
| 55 | + (line-beginning-position) |
| 56 | + t) |
| 57 | + (replace-match |
| 58 | + (save-match-data |
| 59 | + (calc-eval (if currency |
| 60 | + (list |
| 61 | + (concat (match-string-no-properties 0) "+0.0") |
| 62 | + 'calc-float-format |
| 63 | + '(fix 2)) |
| 64 | + (match-string-no-properties 0)))) |
| 65 | + t t) |
| 66 | + (if currency (insert " PLN")))) |
| 67 | +#+end_src |
| 68 | + |
| 69 | +使用方式:在 =12.5*3= 后面按 =M-x fast-calc= ,表达式被替换为 =37.5= 。带前缀参数( =C-u M-x fast-calc= )则保留两位小数并追加货币后缀,比如 =12.5*3= → =37.50 PLN= 。 |
| 70 | + |
| 71 | +原理是 =calc-eval= 接受一个字符串(算术表达式)并返回结果字符串。传入列表形式时可以控制格式—— =(fix 2)= 表示固定两位小数。 =looking-back= 用正则匹配光标左侧的数字和运算符序列, =replace-match= 把匹配到的内容替换为计算结果。 |
| 72 | + |
| 73 | +* Footnotes |
| 74 | + |
| 75 | +[fn:1] [[https://mbork.pl/2026-04-25_How_I_use_my_numeric_keypad_with_Emacs_Ledger_mode][How I use my numeric keypad with Emacs Ledger mode]] |
0 commit comments