居振梁[暴龙] 2009年09月07日 星期一 17:27 | 4522次浏览 | 2条评论
最后,为何要大费周章的做这些事呢?一方面是个人喜好,一方面借应用来检验我的“混合编程插件”的扩展性和健壮性,那可是我的第一个有一定架构的作品哦!或者……如果你想一直写代码,那花个几天时间打造整合个称手的开发环境那是相当值得的!加油!
http:
上篇: 如虎添翼:Console Matlab Vim完美结合[Matlab视角]
环境:Fedora 10+Vim7.2
在上篇里虽然实现了在Matlab Console里运行vim,但关键问题还没解决:vim没法直接跟matlab通信。试想一下(实际情况就是如此),写了一段m script,然后运行没有得到自己的期望或要做参数调整,改吧,于是每改一次都(在matlab console)运行、退出vim,这个看上去没什么问题,但是所有的Undo和Redo信息都丢失了……所以很有必要解决vim直接与matlab通信 的问题。(实际上它们都共同依赖于另一个插件 shellinsidevim.vim ,最新版本并未上传给vim.org)
在继续之前先啰嗦一些题外话。08年年底的时候我写了个vim插件hybridevel.vim,目的是方便混合编程,自己使用一直很满意,这半年 来也陆续修正了一些小bug,但是一直没有完善的文档,因此到此插件到现在也没有发布。既然是“混合编程”,那包含对m script的支持也就理所当然了,但是上次的设计并未考虑到matlab这样的特殊情况,所以花了一点时间来改造那个插件。
本解决方案针对的问题是在不离开vim的情况下执行m script,由于vim对m scrippt的支持本身由hybridevel.vim插件负责,因此vim代码只需考虑最核心的部分。具体来说:
1.使用mlint评估m script,以便发现“编译期”错误和获得提升性能的修改建议:
方便起见,这里用来matlab的外部工具mlint命令,因此实现起来没有什么技术难度。但是这里有两个很郁闷的问题,vim自带了 compiler/mlint.vim文件用于根据其输出生成quickfixlist,但是很奇怪的是在我这里总是匹配不到任何 errorformat,为了弄清楚原因特意一点点对照errorformat规范仔细检查未果。估计不是文件参数的问题,怀疑是vim的bug(这个 vim在分析ant输出时也无法正常解析出错文件名)。所以不再浪费时间,转而根据其原理和vim api手动实现了quickfixlist的生成。顺便也说一点,如果你的vim解析mlint不成问题,也有一个问题需要考虑,mlint的输出中不包 含文件名,除非你一次传递多个文件作为参数,因此在处理单个文件是需要手动规范errorformat,不然即使生成了quickfixlist也不会自 动定位。
function! g
o_matlab_mlint()
call g:ExecuteCommand(g
arseCommand(get(g
roject.current.command,'mlint',"")))
let qfix={'filename':bufname("%"),'lnum':0,'col':0,'vcol':0,'nr':-1,'text':''}
let quickfixes=[]
let hasError=0
for line in split(@+,"n")
if g:Trim(line)==''
continue
endif
if match(line,'^(={10}) .+ 1$')>-1
let qfix['filename']=match(line,'[^= ]+')
continue
endif
let qfix['lnum']=matchstr(line,'^L d+')[2:-1]
let qfix['col']=matchstr(line,'(C d+')[3:-1]
let qfix['text']=matchstr(line,'): u+:.*$')[3:-1]
call add(quickfixes,copy(qfix))
endfor
call setqflist(quickfixes)
cc!
endfunction
2.vim与matlab通信,执行m script并获取执行结果,包括标准输出和figure显示:
实现这个功能的方法也有很多,但是考虑到兼容性、方面性和移植性,这里没有采用诸如“写辅助shell脚本”或“使用vim的编程语言接口(它可能 要求重现编译vim以支持所需的语言)”等很多大型vimscript插件的做法。其实这个需求很简答,不要求与matlab交互,只是将一系列命令发给 matlab然后获取其执行结果而已,使用shell的“文件重定向”特性足以,不过缺点是,即使vim是如上篇所述在matlab里启动的,此方法在实 施时仍然会再启动一个“临时”的matlab。不过由于宿主和子matlab都是“-nodesktop”模式的,系统开销的增加相对于其便捷性也就可以 忽略不计了。
“重定向”的实现有一些小细节,比如“文件”可能是file或named pipe或named socket,这里用的是file,由于file是“非阻塞”的通道,或者说这个通道的另一端一定有接收者,所以得考虑一个顺序,matlab一旦启动就 会输出欢迎信息,然后被stdout接受,而matlab又没有接收到stdin,于是它就直接退出了,显然这样得不到运行结果。而如果是选择named pipe类“阻塞”的通道,只要无视stdout或stderr(如果有的话),matlab就会一直等待,此时无论什么时候给stdin都没问题,当然 由于vimscript是单线程的,自然在matlab等待时没机会给它输入;即使在启动matlab时指名其在后台运行,可以有交互式输入,但是仍然无 法检测stdout或stderr(除非之后不用再交互了)。因此,单凭基本vim script(使用vim编程语言接口比如python的不算)来实现的交互能力是很有限的。
这么一来思路就清晰了,在启动matlab之前先准备好要执行的matlab命令,这里又有一个问题,上篇里我们提到,现代版本的matlab搞得 像个小操作系统,在“-nodesktop”模式下启动的matlab相当于以操作系统桌面为桌面,因此如果执行过程中需要创建小窗口,比如显示图像的 figure,则它相当于一个独立的子程序异步执行。也就是说stdin里的命令会继续执行,等到matlab使命完成退出,然后……失去父进程的小窗口 也跟着强行关闭(好在stdout跑不了,可以少操点心了)。这是个很棘手的问题,大大超出了vim的控制范围,不过既然matlab把自己整的这么伟 大,那是不是它本身能够同步处理小窗口呢?嗯,我猜对了,顺着其exit的help一路找到了uiwait这样的函数。一目了然,stdin的最后一个命 令应该是“uiwait(get(0,'CurrentFigure'))”,等待当前figure退出,如果没有则直接退出,好了,可以慢慢欣赏绘制的 图像了。当然这里只是在等待当前一个窗口,不过这本来就是matlab的责任,所以如果程序需要创建多个figures,劳驾手动代码控制。
向完美主义看起继续处理最后的一些小问题:格式化stdout和stderr,去掉那些乱七八糟的输出和matlab command line控制符,这个就是简单的字符串操作。不过有一点需要说明,stderr即使是在matlab command line里显示也都是干净的,但是其实它的每条错误都被“{^H??? ”和“}^H”包围着(“^H”是ASCII的8)。
function! g
o_matlab_interpret()
let arguments='('.substitute(g
arseCommand("ARGUMENTS"),' ',',','g').')'
let maincmd=fnamemodify(bufname("%"),":t:r").substitute(arguments,'^()$','','g')
let matlabcmds=[]
call add(matlabcmds,"cd ".fnamemodify(bufname("%"),":h"))
call add(matlabcmds,maincmd)
call add(matlabcmds,"uiwait(get(0,'CurrentFigure'))")
try
call writefile(matlabcmds,'.MATLABIN')
let launcher=g
arseCommand(get(g
roject.current.command,'interpret',""))
call g:ExecuteCommand(launcher,'0<.MATLABIN','1>.MATLABOUT','2>.MATLABERR')
let stdout=readfile('.MATLABOUT')
let stdout=['>> '.maincmd]+stdout[match(stdout,'>> >>')+1:-2]+['']
call g:AddShellCommandResult(stdout)
let H=nr2char(
let stderr=join(filter(readfile('.MATLABERR'),'len(v:val)>len("")')," ")
let startpos=match(stderr,'{'.H.'???',0)
while startpos>=0
let endpos=match(stderr,'}'.H,startpos)
call g:AddShellCommandResult(["ExecuteCommand failed: ".stderr[startpos+6:endpos-2],''])
let startpos=match(stderr,'{'.H.'???',endpos)
endwhile
call g
isplayOutput()
finally
call delete('.MATLABIN')
call delete('.MATLABOUT')
call delete('.MATLABERR')
endtry
endfunction
uiwait(get(0,'CurrentFigure'))
至此console、matlab和vim三者的整合告一段落了,根据这次折腾的收获,根据上述思路可以更进一步的完善vim和matlab或其他进程的交互,另外关于那个hybridevel.vim插件,我尽快发布,要不然本文所贴代码就只能看了!呵呵!
最后,为何要大费周章的做这些事呢?一方面是个人喜好,一方面借应用来检验我的“混合编程插件”的扩展性和健壮性,那可是我的第一个有一定架构的作品哦!或者……如果你想一直写代码,那花个几天时间打造整合个称手的开发环境那是相当值得的!加油!
Zeuux © 2024
京ICP备05028076号
回复 電波系山寨文化科学家 2009年09月07日 星期一 18:30
但是
10
回复 居振梁[暴龙] 2009年09月07日 星期一 18:50
但
而且,