Fermat618's Blog

Happy coding

Gtk Tree View 子节点乱跑的问题

pida 有个 ctags 插件,可以从 ctags 分析出来的文件中,新建 tags. 但有时,会发现 tags 乱跑的问题,即某一个父节点的子节点,跑到其它地方去了。

ctags 输出的文件,在程序内部,经分析后,成一个一个的元素。每一个元素,有一个指向父节点的指针。在把这些分立的节点插入进 tree view 的时候,如果子节点先插入,插入时其父节点还未被插入,那么,那些父节点在插入后,不会自动把以前的子节点收进来。

解决这个问题的一个办法,是在插入 tree view 之前,先调整顺序,把一个节点的所有父节点,都放在自身的前面。因为节点可以有分级,父有子,子有孙,递归和 Python 的生成器恰好派上用场,程序看起来就很简洁明了了。

        items = list(....)

        def pop_parent_first(item):
            p = item.parent
            if p is not None and p in items:
                for i in pop_parent_first(p):
                    yield i
            else:
                items.remove(item)
                yield item
        while len(items) != 0:
            item = items[0]
            for x in pop_parent_first(item):
                yield x

第一行,是把原来的元素,做成一个列表。pop_parent_first(item) 函数,在弹出一个对象前,检查有没有父对象,如果有,延后自身弹出,先把它的父对象弹出来。

在 anjuta 中,我又发现了这个问题,觉得很可能是上面这个原因。可惜的是 anjuta 用 C 写的(而不是vala),难找到有用的信息,看了一会,也没找到出错点。

Fortran 中根据语义的补全

Fortran 中的 if 结构,函数声明等,都有不少的信息冗余。如

if (3 > 2) then
    !...
endif

real function add(a, b)  
...
end function

后面的 endif, end function 等,长度都不少。这样虽然提高了可读性,但手动敲下未免还是太麻烦。把时间花在敲入一个很长还必定会有的东西上面,很不划算。如果用 snippet 之类的方案,需要记住一个 key, 然后每次按 tab 展开,除了语言本身,又多了一个记忆 key 的负担。

于是,我想到了一个更为自动化的补全方案,就是让程序根据已经输入的内容,在敲入回车键换行时,自己判定这是不是一个未完成的 if 结构,然后自动在后面补全 endif.

因为这个功能绑定到了一个很重要的按键上,所以功能要尽量的不添乱 。所以,我想到把当前行按照程序语义给解析出来。如果能成功的解析,就补全后面的内容;如果不行,就执行普通的Enter.

解析程序的语义,还可以带来另外的好处。一个是自动加空白,如

do i=1,n

给自动化成为

do i = 1, n

有了空格之后更加的好读。另一个功能是把一些冗余的东西省掉,如

if (a > 3) then

有用的信息只有

if a > 3

因此,只需要敲下上面的文字,再回车,就可以补全成符合 Fortran 语法规范的形式。

代码在此 https://github.com/fermat618/fortran-construct-complete

 

vim打开文件时自动添加cscope连接

cscope是看源代码的得力工具。在编译进了cscope特性的vim中,可以在vim内部方便地使用cscope进行跳转. 使用cscope需要先生成一个默认文件名是cscope.out的交叉引用文件,在vim中使用cscope也需要使用:cscope add 命令指定交叉引用文件所在位置,也即所谓的建立cscope链接。

通常,cscope.out会在一个项目的根目录下面。当在这个目录下打开vim时,使用vim的cscope相关部分给出的示例设置

		if filereadable("cscope.out")
		    cs add cscope.out
		" else add database pointed to by environment
		elseif $CSCOPE_DB != ""
		    cs add $CSCOPE_DB
		endif

可以自动添加cscope.out文件。然而,当在一个项目的子目录下打开一个文件的时候,这份配置却不能正确添加cscope.out文件了。往往用cscope开始查找某个东西的时候,就提示说没有建立cscope链接,很是扫兴。

想做什么事被打断是很不爽的,所以就需要自动添加cscope.out文件了。比较直接的想法就是从当前目录一层一层地向上查找,如果找到了某个cscope.out文件,就把它添加进来。考虑到python3代码写起来比vim脚本爽多了,而我的vim又是自己编译几乎必带这个支持的,所以就在vimscript中用python3来实现了。

if has("cscope") && executable("cscope")
    if has('python3')
        py3 <<EOF
import os, os.path
from itertools import takewhile
def iterate(fun, x):
    yield x
    for element in iterate(fun, fun(x)):
        yield element
for path in takewhile(lambda x: x!='/', iterate(os.path.dirname, os.getcwd())):
    cscopefile = os.path.join(path, 'cscope.out')
    if os.access(cscopefile, os.R_OK): # file readable
        vim.command('cscope add ' + cscopefile +' '+ path)
        break
EOF
    endif
    if $CSCOPE_DB != ""
	cs add $CSCOPE_DB
    endif
endif

用python的好处是容易让代码成为自己想法的自然表达。程序的目的是在当前目录及其父目录中检查cscope.out文件的存在,再添加这个文件,那么这个程序自然的结构就是首先得到当前目录和它的父目录,然后再对这些目录进行一个一个地检查。生成父目录的方式就是对一个目录不停地用basename函数,想到一个生成某个函数对一个值不断应用而产生的序列的函数 iterate(), python 的itertools中竟然没有,那就自己定义一个吧。python的生成器使得程序既不会在当前目录下已经找到cscope.out的时候还去傻傻地生成当前目录的所有父目录的列表,还能让程序看起来很漂亮。用for in结构可比while漂亮。

最后再说下vim中的:cscope add命令。cscope.out文件中需要保存文件所在的位置,而这个位置通常是相对位置。如果vim的当前目录与生成cscope.out时的当前目录不一样,vim就会找不到cscope给出的文件位置。所以,就需要显示式指定cscope.out里面的路径是相对于哪个目录的,这就是cscope add命令的第二个参数。第一个参数自然就是cscope.out文件的路径了。