#title 自动增加数字或者列表 写了一个库可以自动增加数字或者列表。先说两个比较常用的操作,一是如何增 加光标下的数字,二是如何生成一列递增的数字。我的解决方案是用这个包中的 incr-dwim 这个命令。前一个很容易测试,后一个演示如下,比如你有一段文字: one two three 先用矩形操作,在行首加入 1. 这个序号: 1. one 1. two 1. three 然后重新选择刚才的区域(可以直接用 C-x C-x),使用 incr-dwim,是不是就 变成: 1. one 2. two 3. three incr 能增加的类型包括:各种进制的数字(2, 8, 10, 16)、自定义列表、日期、 罗马数字和中文数字。下面详细说明一下。 从简单的开始吧。比如你写了这样的数字: 十进制:7 十六进制:0x07 八进制:07 二进制:100 可以把光标分别放在对应的数字上用对应的命令 incr-digit, incr-hex, incr-oct, incr-bin 来增加数字的值。 一些常见的列表可以用 incr-rotate 来增加,比如: Jan January 星期一 Mon Monday zero 这些列表的定义在 incr-rotate-text 变量里,如果是英文字母构成的词,可以 直接加到这个变量,否则要像我提供的中文星期名字那样,在第一个元素里列出 所有可能的汉字。 增加日期比较麻烦,因为格式太多,而且不好判断类型,所以需要自己来指定类 型。比如: Year/Month/Day: 2006/10/23 Month/Day/Year: 10/23/2006 Day/Month/Year: 24/10/2006 Hour:Min:Second: 19:26:23 当光标在日期上时,用 incr-date 命令会提示你输入日期类型,这四种类型分 别对应 ymd, mdy, dmy, hms,输入其中一种即可。日期的分隔符可以是 -、/、: 三种。 罗马数字的增加用 incr-roman 命令。比如输入 I 后就可以用 incr-roman 来 增加了。 如果你下载了我的[[HanNum][转换数字成汉字的库]],可以把 han-number 加入到 incr-enable-feature 里,再用 load-library 命令重新导入一次 incr 包。这 样就可以用 incr-han-gb, incr-han-big5, incr-han-gb-currency, incr-han-big5-currency 来增加汉字的数字了。如果想始终打开这个选 项,.emacs 文件中一定要在 (require 'incr) 之前写: (setq incr-enable-feature '(number rotate roman date han-number)) 可能现在你已经有点糊涂了,要记这么多命令,有没有更智能一点的命令?所以 我写了 incr-dwim 和 decr-dwim 这两个命令,应该能够满足基本的要求。这个 命令对于十进制数字,以 0x 开头的十六进制数字、列表、日期、罗马数字、汉 字数字的识别应该是没有问题的。比如下面一段话里,任何一个位置上应该都能 正确的调用对应的命令。 十进制:8,十六进制:0x08。 今天是星期一,Oct 23,2006/10/23。 罗马数字:I,一块钱即壹圆整。 除了能增加光标处的文本,还能产生一个递增的序列,比如,写代码里如果想产 生这样的序列: array[1] array[2] array[3] 只要先写一行 array[1],剪切粘贴成: array[1] array[1] array[1] 选中对应矩形区域,用 incr-dwim 就行了。矩形区域的参数传递有点特殊,如 果使用 C-u 5 incr-dwim 就是用 5 为步长,如果用 C-u incr-dwim,这时没有 传递步长,而是告诉 incr-dwim 调用的命令要进行格式化。比如上面的例子, 如果用 C-u incr-dwim 后,会提示你输入步长,格式字符,如果输入步长为 5, 格式字符为 2 就可以生成这样的文本: array[ 1] array[ 6] array[11] 数字的格式化要参考 format 函数的文档,其它的命令,一般会提示输入填充字 符,对齐方式是左对齐还是右对齐。 还有一系列的命令是以 in-region 结尾的。这些命令是对一个区域内的文本使 用相同的数量来增加。比如有这样的一列数字: 6 5 7 想让它们同时增加 3,可以用 C-u 3 M-x incr-digit-in-region。因为这种情 况不太常见,所以没有提供一个 dwim 的命令。 为了让命令具有一定的记忆能力,比如使用 incr-date 后,在这个日期上再次使 用 incr-date 命令就不会提示输入日期类型,会在对应的文字上加上一些属性。 这样带来的副作用是如果错误使用命令,或者想更换命令可能会有问题,这时需 要用 M-x incr-clear-text-property 来清除这些属性。 最后,写一个例子来说明如何自己定义一种新的 incr 命令。比如想要修改 lrc 类型的歌词文件。文件格式如下: [00:00.00]仙剑奇侠传三外传问情篇 主题曲 [00:08.53]仙剑问情 [00:17.47]曲:贾卓伦 词:骆集益 首先需要定义增加时间的方法: (defun incr-lrc-to-second (time) (let ((from (mapcar 'string-to-number (split-string time "[:.]")))) (+ (* 60 (car from)) (cadr from) (* 0.01 (nth 2 from))))) (defun incr-lrc-to-string (time) (format "%02d:%02d.%02d" (floor (/ time 60)) (progn (setq time (mod time 60)) (floor time)) (round (* 100 (- time (floor time)))))) (defun incr-lrc-1 (time arg) (incr-lrc-to-string (+ (incr-lrc-to-second time) arg))) incr-lrc-to-second 可以把字符串转换成秒数,incr-lrc-to-string 可以把秒 数转换成字符串,incr-lrc-1 是直接把一个字符串作参数来增加对应的秒数。 有这样的函数之后写增加命令,应该是相当简单的。首先,确定字符集,比如 lrc 的时间轴的字符集是 "[0-9:.]"。如果要增加光标处的文本,用 incr-at-point 函数,这个函数第一参数是增加的对象调用的函数,对 lrc 时 间轴而言就是前面定义的 incr-lrc-1,第二个参数是增加量,其余的参数都将 直接传递给第一个参数,即 incr-lrc-1。当然对于这个例子而言是不需要的。 所以增加光标处的时间轴的命令可以这样定义: (defun incr-lrc-at-point (arg) (interactive "p") (let ((incr-extend-chars "0-9:.")) (incr-at-point 'incr-lrc-1 (* arg 0.01)))) 增加一个区域的时间轴按前面说的有两种情况,一种是产生递增数列的。 首先要得到第一个时间,这可以用 incr-string-region 得到。然后用 incr-lrc-to-second 转换成秒数。最后调用 incr-apply-on-rectangle 函数。 这个函数接受四个参数,第一个参数是一个函数,它接受两个参数,一个是当前 行区域内的字符串,第二个参数是当前的行数,返回的字符串将被收集起来传递 给 incr-apply-on-rectangle 的第二个参数,这个参数对应的函数要对这个列 表进行处理,返回的新的字符串将按顺序替换原来矩形区域内的文本。如果不需 要修改的话,可以设置为 nil。最后两个参数是矩形区域的开始和终止位置。所 以这个命令最后是这样的: (defun incr-lrc-region (arg beg end) (interactive "p\nr") (let ((incr-extend-chars "0-9:.") from) (setq arg (* arg 0.01) from (incr-lrc-to-second (incr-string-region beg beg end))) (incr-apply-on-rectangle (lambda (text line) (incr-lrc-to-string (+ from (* arg line)))) nil beg end))) 第二种增加的方法是在原来的对象上增加相同的数量。对于 lrc 的时间轴调整 就是这种情况。这个和前一个差别在于传递给 incr-apply-on-rectangle 函数 的第一个参数是不同的: (defun incr-lrc-in-region (arg beg end) (interactive "p\nr") (let ((incr-extend-chars "0-9:.")) (setq arg (* arg 0.01)) (incr-apply-on-rectangle (lambda (text line) (incr-lrc-1 text arg)) nil beg end))) 你可能已经发现 incr-lrc-region 和 incr-lrc-in-region 很多是重复的,而 且认为我在 incr 里命令的行为(在不选中区域时增加光标处的对象,选中区域时 产生一个递增的序列)也挺好的话可以把命令改成这的: (defun incr-lrc-region (arg beg end &optional inplace) (let ((incr-extend-chars "0-9:.") from) (setq arg (* arg 0.01)) (incr-apply-on-rectangle (if inplace (lambda (text line) (incr-lrc-1 text arg)) (setq from (incr-lrc-to-second (incr-string-region beg beg end))) (lambda (text line) (incr-lrc-to-string (+ from (* arg line))))) nil beg end))) (defun incr-lrc (arg) (interactive "p") (if (and mark-active transient-mark-mode) (incr-lrc-region arg (region-beginning) (region-end)) (let ((incr-extend-chars "0-9:.")) (incr-at-point 'incr-lrc-1 (* arg 0.01))))) (defun incr-lrc-in-region (arg beg end) (interactive "p\nr") (incr-lrc-region arg beg end 'inplace)) 这样就基本上可以了。如果你还想加到 incr-dwim 命令里。还要一点改动。一 是给替换的文本加上属性,为配合 decr-dwim,incr-lrc 的参数也要稍微修改 一下。二是要加到 incr-try-alist 里。使用 incr-add-to-alist 可以选择一 个地方插入到 incr-try-alist 里。一定要设置好它在 incr-try-alist 中的位 置,并提供一个尽量严格的测试正则表达式或者函数,这样才能起作用并且不会 干扰其它方法。 (defun incr-lrc-to-string (time) (incr-propertize (format "%02d:%02d.%02d" (floor (/ time 60)) (progn (setq time (mod time 60)) (floor time)) (round (* 100 (- time (floor time))))) 'incr-type 'lrc)) (defun incr-lrc (arg) (interactive "P") (setq arg (incr-prefix-numeric-value arg t)) (if (and mark-active transient-mark-mode) (incr-lrc-region arg (region-beginning) (region-end)) (let ((incr-extend-chars "0-9:.")) (incr-at-point 'incr-lrc-1 (* arg 0.01))))) (incr-add-to-alist '(lrc ((incr-type . lrc) "^[0-9]\\{2\\}:[0-9]\\{2\\}\\.[0-9]\\{2\\}$") incr-lrc "0-9:.") 'digit) 总结一下,这是增加 lrc 的最终版本: (defun incr-lrc-to-string (time) (incr-propertize (format "%02d:%02d.%02d" (floor (/ time 60)) (progn (setq time (mod time 60)) (floor time)) (round (* 100 (- time (floor time))))) 'incr-type 'lrc)) (defun incr-lrc-to-second (time) (let ((from (mapcar 'string-to-number (split-string time "[:.]")))) (+ (* 60 (car from)) (cadr from) (* 0.01 (nth 2 from))))) (defun incr-lrc-1 (time arg) (incr-lrc-to-string (+ (incr-lrc-to-second time) arg))) (defun incr-lrc-region (arg beg end &optional inplace) (let ((incr-extend-chars "0-9:.") from) (setq arg (* arg 0.01)) (incr-apply-on-rectangle (if inplace (lambda (text line) (incr-lrc-1 text arg)) (setq from (incr-lrc-to-second (incr-string-region beg beg end))) (lambda (text line) (incr-lrc-to-string (+ from (* arg line))))) nil beg end))) (defun incr-lrc-in-region (arg beg end) (interactive "p\nr") (incr-lrc-region arg beg end 'inplace)) (defun incr-lrc (arg) (interactive "P") (setq arg (incr-prefix-numeric-value arg t)) (if (and mark-active transient-mark-mode) (incr-lrc-region arg (region-beginning) (region-end)) (let ((incr-extend-chars "0-9:.")) (incr-at-point 'incr-lrc-1 (* arg 0.01))))) (incr-add-to-alist '(lrc ((incr-type . lrc) "^[0-9]\\{2\\}:[0-9]\\{2\\}\\.[0-9]\\{2\\}$") incr-lrc "0-9:.") 'digit)