Archive for May, 2007

Beagle the desktop search tool

Thursday, May 31st, 2007

我所了解的人当中大多数都会把自己的磁盘整理得头头是道,很少有找不到自己的文件的情况,当然我自己也是这样的。可是有些时候使用桌面搜索引擎不是找得到找不到的问题,有时候要定位一个文件确实是比较麻烦的,即使你清楚地知道它在哪里。我自己体会最深刻的就是文档 API 。看看系统自带了多少文档吧:

$ ls -1 /usr/share/doc/ | wc -l
1486

1486 项。挺吓人的数字,每次都输入 /usr/share/doc/qt4-doc/html/qhttp.html 这么一长串东西确实会让人疯掉了。做成浏览器的书签也不行,书签会越来越多,面临同样的状况。这个时候一个桌面搜索引擎确实是很有帮助的。

Beagle 就是这样一个工具。按下 F12 ,搜索框会弹出来,输入关键字,自动将匹配到的文档列出来,点击在浏览器中打开文档,一切变得如此的方便!

使用 apt-get 即可安装 beagle ,按照它的文档上说,默认会建立 /usr/share/doc 等目录的静态索引。我手工运行了 crontab 里面的项目,并重启了 beagled 但是似乎并没有效果。不过没关系,添加动态索引就是了,其实 /usr/share/doc 下的大多数文件并没有用到,置顶索引的时候指定一些自己会用的子目录就好了。

另外,对于 KDE 用户,SUSE 社区贡献了一个 Kerry ,是 beagle 的 KDE 前端,我这里有一个截图:

kerry

只是有时候排序并不是那么理想,期待改进一下,至少文件名匹配的优先级应该高于文件内容匹配的优先级呢。

不愉快的 Qt 之旅

Sunday, May 27th, 2007

今天尝试用 Qt 来写一个小程序,从网上抓取一些东西,并解析一下保存到本地。很简单的功能,但是却写得非常不舒服。

大致看了一下 Qt 提供的库,似乎应有尽有了。一大堆 GUI Widget 可以构建丰富的界面,方便的 QHttp 可以用于下载 Web 页面,并使用 QRegExp 进行解析,还有 Qt 强大的 QTextCodec 可以在各种编码之间进行转换。并且 Qt 4 提供了 MCV 方式,让我能轻松地把事务和视图分开处理。一切都是那么完美,似乎是专门为我准备的一样。然而它们并不是为我准备的。

我相信使用 QHttp 下载文件是非常容易的事情,于是我决定先做界面。我打算做一个列表,其中每一项对应于 Model 的一个下载会话,里面使用 QLabel 以及 QProgressBar 之类的 Widget 组装出一个会话的详细内容的展示,如果可能的话,我还想做成 Compact View 和 Expanded View 两种模式,只对当前选中的项使用 Expanded View 。看上去就像 Esperanza 的播放列表那样。

在翻看了 Qt 所带的示例以及 QListView 、QTableView 等类的 API 等文档之后,我大致了解了 Qt 的 MCV 模式:

  • Model 持有数据。
  • View 负责显示数据。也担当了一种 Controller 的角色。
  • 还有一个叫做 Delegate 的东西,它可以用于实现单项数据的编辑 (通常你有一个 List 或者是 Table 的数据) ,如果要做精细的显示,也可以让 Delegate 取代 View 的显示功能。

现在我只要从 QAbstractItemModel 继承一个自己的 Model 来保存和管理自己的下载会话,再继承一个 QAbstractItemDelegate 用于展示会话, View 应该可以用默认的 QTableView 之类的了。

到这里一切顺利。我再具体查看了下 Model 的工作方式:

  • Model 提供函数让 View 得以知道数据的行数和列数。
  • View 会调用 Model 的 index 函数,得到某行某列的一个元素的索引,这是一个 QModelIndex
  • View 会调用 Model 的 data 函数,传递一个先前获得的索引,来取得特定元素的值。
  • Model 使用诸如 dataChanged 之类的信号通知 View 数据改变了。

于是我决定在 Model 维护一个会话的 List ,每个会话通过信号通知 Model 状态改变,而 Model 再通知 View 更新视图。但是细节上比较麻烦,一堆的会话连接到 Model 上,信号发过来,无法区分是哪个会话,可以为每个会话加一个 id ,然后由 Model 来管理 id ,并在发送信号的时候带上 id 。但是现在是 View 来向 Model 索取数据,它使用行和列的索引作为标识,一个想法是把 id 就做成行号,于是某个 id 的会话发送信号说状态改变了,我正好可以就那个 id 告诉 View 第几行的数据改变了。可是如果我想要移除下载完成的会话的话,这样就不好办了,移除会话以后剩下的会话的行号就要变了,而 id 并没有得到自动同步。每次,某个 id 的会话报告状态改变, Model 需要查找一下所有的行,看哪一行对应到该 id ,然后通知 View 该行改变了。这显得非常麻烦,似乎是由于 View 强行使用行列作为数据的索引造成的。不过没关系,我干脆直接报告所有的行都变化了就好了。

而 Delegate 也相当复杂,想要实现编辑,可以提供一个 Widget ,然后显示的时候却要实现 paint 函数。我更希望使用一系列 Widget 组装起来实现一个 View 的显示,而不是自己去画。我搜索了一下邮件列表,发现 Qt 现在确实不提供这种便利,只有自己画,还好 Qt 提供了一些可以画出基本 Widget 的函数,可是诸如 Layout 之类的功能就只能自己来管理了。还有一个地方相当奇怪,我在下载了一些真实程序的源代码浏览了之后,发现 View 从 Model 那里取数据的时候还有一个 Role 参数,而 Role 不同的时候期望返回不同的东西,其中有字体、颜色、背景以及图标等,这些原本应该由 View 来处理的东西竟然全部要从 Model 那里取!最近时常用 Ruby on Rails 写一点 Web 应用,觉得 MCV 模式使用起来很顺,但是现在用 Qt 的这套构架却觉得异常别扭。

经过痛苦地编码,最后终于实现了一个还比较理想的 UI 界面。现在看来,还是 HTML + CSS 写界面舒服啊,虽然 HTML 这么多年也饱经沧桑了,但是毕竟是专门用于做界面的语言。我想不久以后在桌面应用程序这一块的专用 UI 语言也该大行其道了吧 (如果那个时候还有桌面应用的话) ,好像 Mozilla 和微软都有这类似的东西吧 ( XUL 以及 XAML 之类的东西 ) ?

接下来是下载,QHttp 用起来还是很方便的,下载之后用 QTextCodec 进行编码转换,然后使用 QRegExp 进行解析。查看了文档,发现 QRegExp 居然不支持多行搜索!后来发现下载的文件一直不对。跟踪了好久,发现是 QHttp 在指定要下载的路径的时候比较麻烦,而用了 QUrl 反而好像是在帮倒忙,结果资源位置都指定错了。

我决定让 Qt 的体验和这个程序暂时告一段落。今天的经历实在是让原本在我心目中地位很高的 Qt 程序库一下子降低了许多。用 Qt 构建出来的 KDE 用起来觉得处处设计都很贴心,而今天用 Qt 却好像感觉很多地方该有的功能没有,不该有的功能一大堆,特别别扭。所谓更换工作就是最好的休息,先把这个小程序放一放好了,要不然要对 Qt 积攒太多偏见了。

Play with macro

Friday, May 25th, 2007

Lisp 的宏可谓是异常强大。我所接触过的宏大约算三种:

  • 一种是 C 语言的宏,这几乎可以算是功能最弱但又用得最多的宏了。只做非常简单的语法分析,并进行文本替换。但是实际上这种简单的宏为 C/C++ 带来了许多额外的能力,不过从来这个东西好像也没有专门的文献以及教材详细讲解,大多是经验丰富的程序员们通过源代码互相传播关于宏的知识,而且许多方面在各个不同的编译器上的结果都是不一样的,所以一直以来宏也只有那部分非常常见的用法为大家所广泛接收并使用。事实上,如果你感兴趣,可以去看一看 boost.preprocessor ,你会了解到其实宏可以做很多很多的事情。
  • 一种是 C++ 的模板,我把这看作一种宏,因为模板也是操纵代码,而并不在运行期存在。C++ 的模板比起原始的宏来说要强大了许多,它会进行语法分析,并且藉由篇特化等特性让它具有了许多意想不到的能力。我不知道 C++ 模板最初设计出来是不是有一点取代宏的初衷,不过现在看来似乎完全没有走在这个方向。模板和宏都有各自的用处。模板+内联函数+常量等特性可以在一定程度上取代宏,但是宏仍然有它生存的地方。而模板似乎是刚刚找到了自己真正的方向,发展出如今鼎鼎大名的“范型编程” (GP) 。我对模板也处于刚刚开始了解的状态,不过模板似乎还处在发展初期,不像 OOP 一类的技术,先有了一套已经研究透了的理论,才拿出来实现,模板似乎是无心插柳柳成荫,做出模板这个特性以后突然发现原来模板还有这样那样的用法,实现各种各样特性的方法被人们发掘出来,种种特性令人振奋!然而也正是因为初衷并不是用来做那些事情的吧,即使可以技巧性很强地实现那些范型技术,可是却有许多尴尬的地方,例如如果出现编译错误,往往错误信息是风马牛不相及,编译速度过慢,不方便查看模板扩展后的结果,不便于调试,而且模板的种种变态的用法甚至会轻松把编译器搞挂掉。因此,模板相关的技术都需要更进一步地发展,才能更广泛地投入工业使用啊,要不然就只有作为学院派的高级玩具了。明年 C++0x 就要出来了,也算是非常令人期待的了。
  • 最后就是 Lisp 的宏了。不管是老式的 C 宏还是 C++ 的新式模板,都是独立于原来的语言的另一种语言,他们运行于编译期(如果把预处理也包含在编译期内的话)。而 Lisp 的宏与它们的最大的区别就是,Lisp 宏与 Lisp 本身是相同的一种语言,完全相同,只不过宏运行于编译期。加上 Lisp 强大的表处理能力,就能对它自己进行随心所欲的控制了(Lisp 语言本身是由表组成的)。这往往让 Lisp 的宏成为独特的威力强大的完全区别于基于文本替换的宏以及更高级的模板等技术。

宏的一个常见的用途就是用于定义“新的语言”。Steve Bourne 在为 Unix Version 7 写 shell 的时候 (就是著名的 Bourne Shell) 曾经用宏让 C 语言“变成了” Algol-68:

1
2
3
4
5
6
7
8
9
10
11
#define STRING char *
#define IF if (
#define THEN ) {
#define ELSE } else {
#define FI ;}
#define WHILE while (
#define DO ) {
#define OD ;}
#define INT int
#define BEGIN {
#define END }

然后他这样写代码:

1
2
3
4
5
6
7
8
9
10
11
INT compare(s1, s2)
    STRING s1;
    STRING s2;
BEGIN
    WHILE *s1++ == *s2
    DO IF *s2++ == 0
        THEN return (0);
        FI
    OD
        return(*--s1 - *s2);
END

当然他这样的做法遭到众人的抗议,而且 Bourne Shell 的代码也一直被认为是难以维护的典范。我在这里举这个例子并不是为了说明使用宏让语言变成另外一个样子是不好的做法。事实上,我们经常需要这样做,小到 syntax sugar ,大到 DSL (Domain Specific Language) ,我们到处都在使用宏的这种特性。而 Lisp 的这种强大的宏让事情变得更加普遍,事实上,在 Common Lisp 里面,就有一个强大的 loop 宏,它非常灵活,采用了更类似于命令式语言的风格 (例如我们熟悉的 for , while 以及 return 等) ,而不是 Lisp 原本的 map 的风格 (也因此一直不为一些保守的 Lisper 们所接受) ,另外,还有专门的宏让你可以使用中缀表达式来写 Lisp 程序 (许多人把 Lisp 的前缀表达式作为拒绝的理由) ,等等。而在于 DSL 的领域, Lisp 也和通常的其他语言的做法不同,它采用一种自底向上的做法,在 Lisp 语言本身的基础上,通过宏和表操作,构建更高层的语言应用,结果 DSL 本身其实又是 Lisp ,只不过处在更高一层,但是仍然可以使用所有最底层的 Lisp 的功能。Paul Graham 在 Programming Bottom-Up 进行了阐述。

在写 Lisp 的宏的时候,通常只要写出期望的原来的样子和期望得到的结果的样子,剩下的工作就会变得很容易了。下面我用我刚刚在 Elisp 里面做的宏作为例子来简单地介绍一下。我在前一篇 Blog 里面介绍了 smart-snippet ,可以方便地为各个 mode 定义 snippet 。然而许多时候几个不同的 mode 可以定义相同的 snippet ,例如 c-modec++-modejava-mode 可以使用相同的 snippet 。然而原来的 snippet 代码不能方便地为几个不同的 mode 定义相同的 snippet 和 key-binding 。考虑实现这个功能,我希望我在定义的时候可以这样写:

1
2
3
4
5
6
7
(smart-snippet-with-abbrev-tables
 (java-mode-abbrev-table
  c++-mode-abbrev-table
  c-mode-abbrev-table)
 
 ("if" "if ($${condition})\n{$>\n$>$.\n}$>" 'bol?)
 ("else" "else\n{$>\n$>$.\n}$>" 'bol?))

来实现分别为三个 mode 定义两个 snippet 的功能,如果手写,会写成这样:

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
(progn
  (smart-snippet-abbrev
   'java-mode-abbrev-table
   "if"
   "if ($${condition})\n{$>\n$>$.\n}$>"
   'bol\?)
 
  (smart-snippet-abbrev
   'java-mode-abbrev-table
   "else"
   "else\n{$>\n$>$.\n}$>"
   'bol\?)
 
  (smart-snippet-abbrev
   'c++-mode-abbrev-table
   "if"
   "if ($${condition})\n{$>\n$>$.\n}$>"
   'bol\?)
 
  (smart-snippet-abbrev
   'c++-mode-abbrev-table
   "else"
   "else\n{$>\n$>$.\n}$>"
   'bol\?)
 
  (smart-snippet-abbrev
   'c-mode-abbrev-table
   "if"
   "if ($${condition})\n{$>\n$>$.\n}$>"
   'bol\?)
 
  (smart-snippet-abbrev
   'c-mode-abbrev-table
   "else"
   "else\n{$>\n$>$.\n}$>"
   'bol\?))

可以看到,首先,第一个参数是一个表,有点类似于和后面的参数做笛卡尔积的感觉。这需要一个双重嵌套的循环,没有问题,我们可以使用 loop 宏来完成,大概会像这个样子:

1
2
3
4
(defun double-loop (list1 &rest list2)
  (loop for i in list1
        collect (loop for j in list2
                      collect (list 'func i j))))

而调用 (double-loop '(1 2 3) 4 5) 就会得到这样的结果:

1
2
3
4
5
6
(((func 1 4)
  (func 1 5))
 ((func 2 4)
  (func 2 5))
 ((func 3 4)
  (func 3 5)))

得到了 (func 1 4) 这样的结构了,稍微改一下就可以用于定义单个的 snippet 。然而这里并不是我们想要的,虽然是嵌套循环,我们想要的是最后的表 (也就是 func 的函数调用) 需要处于同一层。于是我们需要一个 flatten-1 来去掉一层多余的表结构:

1
2
3
4
5
6
7
(defun flatten-1 (list)
  (cond ((atom list) list)
	((listp (car list))
	 (append (car list)
		 (flatten-1 (cdr list))))
	(t (append (list (car list))
		   (flatten-1 (cdr list))))))

调用 (flatten-1 (double-loop '(1 2 3) 4 5)) 就可以得到想要的结果了:

1
2
3
4
5
6
((func 1 4)
 (func 1 5)
 (func 2 4)
 (func 2 5)
 (func 3 4)
 (func 3 5))

下面就可以写做宏的形式:

1
2
3
4
5
6
7
8
9
10
11
(defmacro smart-snippet-with-abbrev-tables
  (abbrev-tables &rest snippets)
  `(progn
     ,@(smart-snippet-flatten-1
	(loop for table in abbrev-tables
	      collect (loop for snippet in snippets
			    collect (append
				     (list
				      'smart-snippet-abbrev
				      table)
				     snippet))))))

然后我们可以看一下效果,使用 M-x pp-eval-last-sexp 来执行下面的代码:

1
2
3
4
5
6
7
(macroexpand '(smart-snippet-with-abbrev-tables
 (java-mode-abbrev-table
  c++-mode-abbrev-table
  c-mode-abbrev-table)
 
 ("if" "if ($${condition})n{$>n$>$.n}$>" 'bol?)
 ("else" "elsen{$>n$>$.n}$>" 'bol?)))

就可以看到如下的结果:

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
(progn
  (smart-snippet-abbrev
   java-mode-abbrev-table
   "if"
   "if ($${condition})n{$>n$>$.n}$>"
   'bol\?)
 
  (smart-snippet-abbrev
   java-mode-abbrev-table
   "else"
   "elsen{$>n$>$.n}$>"
   'bol\?)
 
  (smart-snippet-abbrev
   c++-mode-abbrev-table
   "if"
   "if ($${condition})n{$>n$>$.n}$>"
   'bol\?)
 
  (smart-snippet-abbrev
   c++-mode-abbrev-table
   "else"
   "elsen{$>n$>$.n}$>"
   'bol\?)
 
  (smart-snippet-abbrev
   c-mode-abbrev-table
   "if"
   "if ($${condition})n{$>n$>$.n}$>"
   'bol\?)
 
  (smart-snippet-abbrev
   c-mode-abbrev-table
   "else"
   "elsen{$>n$>$.n}$>"
   'bol\?))

已经很接近结果了,唯一的不足就是我们应该使用 'c-mode-abbrev-table 而不是没有 quote 的形式。这个也好办,我们写一个函数来为作为第一个参数的包含所有 abbrev-table 的表的每一个元素加上 quote

1
2
3
(defun smart-snippet-quote-element (list)
  (loop for item in list
	collect (list 'quote item)))

最后把这个函数加进去,就得到完整版的 smart-snippet-with-abbrev-tables 宏:

1
2
3
4
5
6
7
8
9
10
11
12
(defmacro smart-snippet-with-abbrev-tables
  (abbrev-tables &rest snippets)
  (let ((tables (smart-snippet-quote-element abbrev-tables)))
    `(progn
       ,@(smart-snippet-flatten-1
	  (loop for table in tables
		collect (loop for snippet in snippets
			      collect (append
				       (list
					'smart-snippet-abbrev
					table)
				       snippet)))))))

用同样的一些工具函数,我还做了 smart-snippet-with-keymap 宏,而且它们的结构都很类似,我甚至可以把这个结构抽象出来,定义一个新的宏,比如,叫做 smart-snippet-def-with ,然后使用这个宏来定义 with-abbrev-tablewith-keymap 宏,来达到代码重复程度最小化 (事实上,名如 def-xxx 的宏在 ELisp 里面是非常常见的) 。

当然,宏的用法并不局限于此。Paul Graham 在他的《On Lisp》一书中描述了大量 Lisp Macro 的技巧,如果感兴趣,可以找来阅读一下。

110 周年校庆

Monday, May 21st, 2007

今天是学校 110 周年生日啦,学校发了餐票,可以在餐厅免费就餐。去吃饭的时候就被吓坏了,有“亚洲最大的食堂”的称号的餐厅竟然挤得水泄不通!我不太喜欢人太多,不过看到今天校庆这么热闹,也是很开心的。晚上还有校庆晚会,可惜抽签没有抽到,没有拿到票,不能去现场看,只能在寝室看直播了:

mplayer -cache 8000 -af pan=1:1 mms://10.10.5.81/zjulive

晚会也是很好看的啊。如果有清晰版的 DVD 卖的话,一定要买几张,送给亲友,自己也留个纪念。

当然,也衷心祝福学校生日快乐,祝愿浙大越办越好!

smart-snippet and smart-skeleton

Monday, May 21st, 2007

随着 Ruby on Rails 火起来,TextMate 也突然变得很火。没有 Mac 机器,不能体验到 TextMate 是什么感觉,不过在网络上看到 TextMate 的视频演示,其中有一个功能确实是很不错的。就是那个 snippet 。定义一些模板,然后在合适的时候展开,以减少输入重复的内容,这个是每个稍微强一点的编辑器都有的功能了,同一个“域”,在多处展开也是非常常见的功能。但是通常的编辑器都是对每个会放到多个地方的“域”进行提问(例如,弹出一个对话框),TextMate 让人耳目一新的地方就是它并不弹出对话框进行提问,而是直接让你在“域”的地方输入,并在输入的同时同步更新其他几个相关联的“域”的内容。

同步更新与原来的一个一个提问的方法相比,简直就是太 Cool 了!一时间各大编辑器也开始模仿这个功能。Emacs 自然也不落后,很快就有人做出了 snippet.el ,在 Emacs 里面实现了 TextMate 的那种很 Cool 的功能。

snippet.el 适用 Emacs 内建的 abbrev 的功能来实现。Emacs 的 abbrev 与其他一些编辑器不一样,它不需要某一个特定的快捷键来触发(或者说,有许多不同的“快捷键”可以让它展开),当你打开 abbrev-mode 之后,它会在你键入的过程中自动展开,输入一个单词,然后键入空格、标点符号、回车等所有可以作为单词分界的内容时,abbrev 就会被展开。内置的 abbrev 以 major-mode 为单位,可以为不同的 major-mode 定义不同的 abbrev-table 。然而,内建的 abbrev 还是有几个不太方便的地方

  1. 由于是使用单词边界自动触发,因此无法定义非单词的 abbrev ,例如,不能使用 abbrev 让 “(” 自动展开为 “()”。
  2. 以 major-mode 为单位有时候还是显得粒度不够细。例如,我为 c++-mode 定义了一个把 “class” 展开为一个定义 class 的框架,这样很方便,但是我不想在我写注释的时候输入 “class” 却突然展开出一大堆东西。通常的解决办法有两种:
    • 手工不触发 abbrev ,就是在输入 “class” 之后的一个字符,例如,要输入 “SPACE”,现在使用 “C-q SPACE” 来键入。
    • 更改 abbrev ,例如,定义 “classx” ,在需要展开的时候输入 “classx” ,而不影响正常的输入。msf-abbrev.el 就是选择的这种方法

    然而,实际上,两种方法都不是那么舒服,关键就是 Emacs 没有区分在同一个 major-mode 里面的不同语法上下文。

于是我着手做了一个基于 snippet.el 的 smart-snippet.el ,提供更细粒度的控制,正如其名字那样,它更聪明。允许你定义不同上下文展开为不同的模板,或者干脆不展开。非常好用。

我使用 snippet.el 的展开引擎来解析和展开模板,并做了一些小小的更改。 snippet.el 的模板语法非常简单:

  • $${field} 定义一个域,同名的域在编辑的时候会同步更新。使用 Tab 和 S-Tab 在各个域之间移动。
  • $. 最后光标所处的位置。
  • $> 表示进行一次自动缩进。Emacs 通常对各个 major-mode 都提供非常好的自动缩进功能。

并且这些语法都是可以定制的,如果它们和某一种语言的语法冲突,导致 Emacs 缩进的时候被搞晕了的话,可以更改为其他不会引起混乱的标记,你可以为不同的 major-mode 定义不同的一套模板语法。例如,为 c++-mode ,我可以定义 if 在正常情况下展开为

if ($${cond})
{$>
$>$.
}$>

而在其他情况,例如字符串或者注释里面就不展开。还有其他一些语言,如 Perl 、Ruby 等,同样的关键字有不同的用法。例如,在 Ruby 里面,if 就有这两种写法

# one way
if cond
  do_something
end

# another way
do_something if cond

smart-snippet.el 的 smart 就在这里能派上用场了!我在项目主页上上传了一个视频演示,展示了 smart-snippetl.el 的功能。

然而,Emacs 内建的 abbrev 的另外一个不足还没有解决。其实这个很好解决,只需要把引号、括号等键绑定到相应的输入一对引号、一对括号的函数上就可以了。Emacser 们通常都使用 Emacs 自带的 skeleton 功能来解决这个问题。然而 smart-snippet.el 在这里仍然能派上用场。我已经扩展了 smart-snippet.el ,让你能够轻松地把一个 snippet 绑定到一个键上。你可以

  • " 在普通代码里面展开为 "$." ;而如果它本来就在字符串里面,则展开为一个转义的引号 \"
  • < 在正常情况下不展开(作为小于号),而当你已经键入了 template 接下来要写模板参数的时候,展开为 < $. >
  • ……任何你能想到的!

关于具体如何配置以及更多详细的内容,可以参考 smart-snippet.el 的项目主页

最后,我再说一点和 smart-snippet 关系不那么大的内容:在让 " 扩展为 "$." 之后,如何“跳出”引号(也就是把光标移动到引号的后面)呢?我目前所知的有几种解决方案:

  • 直接用方向键,但是使用 Emacs 或者 Vim 的人通常都会觉得方向键太远了,很难按。当然 Emacs 和 Vim 都有专门用于移动光标的快捷键(Emacs 里面可以使用 C-f ,而 Vim 里面虽然直接 l 就可以了,但是却要先按一下 ESC),不过如果是对于非常“懒”的人来说的话,他们仍然是很麻烦的。
  • 一些编辑器会允许你直接输入 " ,它会进行判断,并覆盖掉当前这个引号,并把光标移动到后面,不过这似乎有些丧失了原来自动扩展为一对引号的意义了,到头来还是要自己手工输入后面那个引号。从自动扩展得到的唯一的好处就是不会在输入一个引号的时候,后面整个一片被解释为字符串,显示一片语法高亮,看上去很不爽,让你迫不及待地想赶紧加上另外一个引号,让编辑器能正确地解析语法高亮。
  • 使用一个通用的快捷键来“跳出”。例如 Emacs 的一个扩展 cdlatex 作为一个“让输入变成享受”的典范,就使用的这种方法,它使用一个非常好按的键:Tab 键来完成这个功能。

我在这里也提供一个针对 c++-mode 的“跳出” snippet 的函数。其实 Tab 真的被用的太多了,通常我们写一个包装函数,用于在不同的环境下让 Tab 来完成不同的工作,但是 Emacs 通常有各种扩展,为了避免各个扩展之间的兼容性,对于这种“核心”的功能键,一般还是不要改为妙。幸运的是,Emacs 在 X 模式下(就是说,不是通过 -nw 选项来运行的终端模式)有“两个” Tab 可以用:

  • C-i 也就是通常认为的 Tab ,在终端下是不区分 TabC-i 的。
  • 这个才是真正的对应到键盘上的那个 Tab 键。

于是我们就可以分别用 C-i 来做不同的事情了。

;; jump out from a pair(like quote, parenthesis, etc.)
(defun kid-c-escape-pair ()
  (interactive)
  (let ((pair-regexp "[^])}\"'>]*[])}\"'>]"))
    (if (looking-at pair-regexp)
	(progn
	  ;; be sure we can use C-u C-@ to jump back
	  ;; if we goto the wrong place
	  (push-mark)
	  (goto-char (match-end 0)))
      (c-indent-command))))
;; note TAB can be different to  in X mode(not -nw mode).
;; the formal is C-i while the latter is the real "Tab" key
;; in your keyboard.
(define-key c++-mode-map (kbd "TAB") 'kid-c-escape-pair)
(define-key c++-mode-map (kbd "") 'c-indent-command)
;; snippet.el use TAB, now we need to use 
(define-key snippet-map  (kbd "") 'snippet-next-field)

其实编辑器的设计也是一门学问呢!让程序员能够更舒服地写代码,无疑是非常重要的话题。

引用已经析构了的局部变量

Monday, May 21st, 2007

在平常的编程里面,这样的代码算是再普通不过了:

void func()
{
    SomeType local_obj;
    another_func(local_obj);
    // ...
}

然而最近我却遇到了问题。在使用 boost::asio 的时候,使用了这种常规的做法。结果遭遇段错误。后来才意识到是在进行异步调用。 another_func 拿着 local_obj (事实上,真正的代码是一个 local 的容器,我把它的 iterator 传递到了一个异步调用里面)却在 func 返回的时候就已经被析构了。不过,在异步调用里面也可以很容易地自动管理对象的生存期,很常用的方法就是使用 boost::shared_ptr 了,在 asio 的例子里面也随处可见。

现在刚刚开始接触异步调用、多线程编程方面的东西,很多东西还不熟悉,需要多多实践啊。

视力下降了

Monday, May 21st, 2007

最近真正有些感觉到不对劲了,看一些稍微远一点的东西竟然开始模糊起来。也许视力是有些下降了。我可不喜欢戴眼镜,觉得戴上眼镜之后不管做什么都太不方便了。眼睛还是得要好好保养。要减少在电脑前的时间,多一点休息时间和阅读时间,要不然身体糟糕了就一切都没有了。可惜杭州这个地方地势太平坦,周围的高楼一建起来,就什么都看不到了,仅有的绿色就是院子里面的几棵桔子树了,远眺也是一片空白,或者是乌云,或者是飞机拖出来的一跳长长的云尾。相比之下,还是更喜欢家乡那边的大山。