#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)