info.vim 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397
  1. " GNU Info browser
  2. "
  3. " Copyright (c) 2001, 2002 Slavik Gorbanyov <rnd@web-drive.ru>
  4. " All rights reserved.
  5. "
  6. " Redistribution and use, with or without modification, are permitted
  7. " provided that the following conditions are met:
  8. "
  9. " 1. Redistributions must retain the above copyright notice, this list
  10. " of conditions and the following disclaimer.
  11. "
  12. " 2. The name of the author may not be used to endorse or promote
  13. " products derived from this script without specific prior written
  14. " permission.
  15. "
  16. " THIS SCRIPT IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
  17. " OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  18. " WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  19. " DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
  20. " INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  21. " (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  22. " SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  23. " HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  24. " STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
  25. " IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  26. " POSSIBILITY OF SUCH DAMAGE.
  27. "
  28. " $Id: info.vim,v 1.7 2002/11/30 21:59:05 rnd Exp $
  29. let s:infoCmd = 'info --output=-'
  30. if has('win32')
  31. let s:infoBufferName = '-Info- '
  32. else
  33. let s:infoBufferName = 'Info: '
  34. endif
  35. let s:dirPattern = '^\* [^:]*: \(([^)]*)\)'
  36. let s:menuPattern = '^\* \([^:]*\)::'
  37. let s:notePattern = '\*[Nn]ote\%(\s\|$\)'
  38. let s:indexPattern = '^\* [^:]*:\s*\([^.]*\)\.$'
  39. let s:indexPatternHL = '^\* [^:]*:\s\+[^(]'
  40. command! -nargs=* Info call s:Info(<f-args>)
  41. fun! s:Info(...)
  42. let file = "(dir)"
  43. let node = "Top"
  44. if a:0
  45. let file = a:1
  46. if file[0] != '('
  47. let file = '('.file.')'
  48. endif
  49. if a:0 > 1
  50. let node = a:2
  51. let arg_idx = 3
  52. while arg_idx <= a:0
  53. exe 'let node = node ." ". a:'.arg_idx
  54. let arg_idx = arg_idx + 1
  55. endwhile
  56. endif
  57. endif
  58. call s:InfoExec(file, node)
  59. if winheight(2) != -1
  60. exe 'resize' &helpheight
  61. endif
  62. endfun
  63. fun! s:InfoExec(file, node, ...)
  64. " a:0 != 0 means last node requested
  65. if a:0
  66. let line = a:1
  67. else
  68. let line = 1
  69. endif
  70. if a:0 == 0 && exists('b:info_file')
  71. let last_file = b:info_file
  72. let last_node = b:info_node
  73. let last_line = line('.')
  74. endif
  75. let bufname = s:infoBufferName.a:file.a:node
  76. if buflisted(bufname) && a:0 < 2
  77. if &ft == 'info'
  78. silent exe 'b!' escape(bufname, '\ ')
  79. else
  80. silent exe 'sb' escape(bufname, '\ ')
  81. endif
  82. else
  83. if &ft == 'info'
  84. let command = 'e!'
  85. else
  86. let command = 'new'
  87. endif
  88. silent! exe command "+exe'setlocal''modifiable''noswapfile''buftype=nofile''bufhidden=delete'" escape(bufname, '\ ')
  89. setf info
  90. let cmd = s:infoCmd." '".a:file.a:node."' 2>/dev/null"
  91. " handle shell redirection portable
  92. if $OS !~? 'Windows'
  93. let save_shell = &shell
  94. set shell=/bin/sh
  95. endif
  96. let save_shellredir = &shellredir
  97. set shellredir=>
  98. exe "silent 0r!".cmd
  99. let &shellredir = save_shellredir
  100. if $OS !~? 'Windows'
  101. let &shell = save_shell
  102. endif
  103. call s:InfoBufferInit()
  104. endif
  105. let b:info_file = a:file
  106. let b:info_node = a:node
  107. if exists('last_file')
  108. let b:info_last_file = last_file
  109. let b:info_last_node = last_node
  110. let b:info_last_line = last_line
  111. endif
  112. setlocal nomodifiable
  113. if s:InfoFirstLine()
  114. exe line
  115. else
  116. echohl ErrorMsg | echo 'Info failed (node not found)' | echohl None
  117. endif
  118. endfun
  119. fun! s:InfoBufferInit()
  120. " remove all insert mode abbreviations
  121. iabc <buffer>
  122. if has("syntax") && exists("g:syntax_on")
  123. syn case match
  124. syn match infoMenuTitle /^\* Menu:/hs=s+2,he=e-1
  125. syn match infoTitle /^[A-Z][0-9A-Za-z `',/&]\{,43}\([a-z']\|[A-Z]\{2}\)$/
  126. syn match infoTitle /^[-=*]\{,45}$/
  127. syn match infoString /`[^`]*'/
  128. exec 'syn match infoLink /'.s:menuPattern.'/hs=s+2'
  129. exec 'syn match infoLink /'.s:dirPattern.'/hs=s+2'
  130. exec 'syn match infoLink /'.s:indexPatternHL.'/hs=s+2,he=e-2'
  131. syn region infoLink start=/\*[Nn]ote/ end=/\(::\|[.,]\)/
  132. if !exists("g:did_info_syntax_inits")
  133. let g:did_info_syntax_inits = 1
  134. hi def link infoMenuTitle Title
  135. hi def link infoTitle Comment
  136. hi def link infoLink Directory
  137. hi def link infoString String
  138. endif
  139. endif
  140. " FIXME: <h> is move cursor left
  141. noremap <buffer> h :call <SID>Help()<cr>
  142. noremap <buffer> <CR> :call <SID>FollowLink()<cr>
  143. noremap <buffer> <C-]> :call <SID>FollowLink()<cr>
  144. " FIXME: <l> is move cursor right
  145. " noremap <buffer> l :call <SID>LastNode()<cr>
  146. noremap <buffer> ; :call <SID>LastNode()<cr>
  147. noremap <buffer> <C-T> :call <SID>LastNode()<cr>
  148. noremap <buffer> <C-S> /
  149. " FIXME: <n> is go to next match
  150. " noremap <buffer> n :call <SID>NextNode()<cr>
  151. noremap <buffer> . :call <SID>NextNode()<cr>
  152. noremap <buffer> p :call <SID>PrevNode()<cr>
  153. noremap <buffer> > :call <SID>NextNode()<cr>
  154. noremap <buffer> < :call <SID>PrevNode()<cr>
  155. noremap <buffer> u :call <SID>UpNode()<cr>
  156. noremap <buffer> t :call <SID>TopNode()<cr>
  157. noremap <buffer> d :call <SID>DirNode()<cr>
  158. noremap <buffer> s :call <SID>Search()<cr>
  159. noremap <buffer> <TAB> :call <SID>NextRef()<cr>
  160. nnoremap <buffer> q :q!<cr>
  161. noremap <buffer> <Space> <C-F>
  162. noremap <buffer> <Backspace> <C-B>
  163. runtime info-local.vim
  164. endfun
  165. fun! s:Help()
  166. echohl Title
  167. echo 'Info browser keys'
  168. echo '-----------------'
  169. echohl None
  170. echo '<Space> Scroll forward (page down).'
  171. echo '<Backspace> Scroll backward (page up).'
  172. echo '<Tab> Move cursor to next hyperlink within this node.'
  173. echo '<Enter>,<C-]> Follow hyperlink under cursor.'
  174. echo ';,<C-T> Return to last seen node.'
  175. echo '.,> Move to the "next" node of this node.'
  176. echo 'p,< Move to the "previous" node of this node.'
  177. echo 'u Move "up" from this node.'
  178. echo 'd Move to "directory" node.'
  179. echo 't Move to the Top node.'
  180. echo '<C-S> Search forward within current node only.'
  181. echo 's Search forward through all nodes for a specified string.'
  182. echo 'q Quit browser.'
  183. echohl SpecialKey
  184. echo 'Note: "," means "or"'
  185. echohl None
  186. endfun
  187. fun! s:InfoFirstLine()
  188. let b:info_next_node = ''
  189. let b:info_prev_node = ''
  190. let b:info_up_node = ''
  191. let line = getline(1)
  192. let node_offset = matchend(line, '^File: [^, ]*')
  193. if node_offset == -1
  194. return 0
  195. endif
  196. let file = strpart(line, 6, node_offset-6)
  197. if file == 'dir'
  198. return 1
  199. endif
  200. " let file = substitute(file, '\(.*\)\.info\(\.gz\)\=', '\1', '')
  201. let b:info_next_node = s:GetSubmatch( line, '\s\+Next: \([^,]*\),')
  202. let b:info_prev_node = s:GetSubmatch( line, '\s\+Prev: \([^,]*\),')
  203. let b:info_up_node = s:GetSubmatch( line, '\s\+Up: \(.*\)')
  204. return 1
  205. endfun
  206. fun! s:GetSubmatch(string, pattern)
  207. let matched = matchstr(a:string, a:pattern)
  208. if matched != ''
  209. let matched = substitute(matched, a:pattern, '\1', '')
  210. endif
  211. return matched
  212. endfun
  213. fun! s:NextNode()
  214. if exists('b:info_next_node') && b:info_next_node != ''
  215. \ && match(b:info_next_node, '(.*)') == -1
  216. call s:InfoExec(b:info_file, b:info_next_node)
  217. return 1
  218. else
  219. echohl ErrorMsg | echo 'This is the last node' | echohl None
  220. endif
  221. endfun
  222. fun! s:TopNode()
  223. if b:info_node == 'Top'
  224. if b:info_file == '(dir)'
  225. echohl ErrorMsg | echo 'Already at top node' | echohl None
  226. return
  227. endif
  228. let file = '(dir)'
  229. let node = b:info_node
  230. else
  231. let file = b:info_file
  232. let node = 'Top'
  233. endif
  234. call s:InfoExec(file, node)
  235. endfun
  236. fun! s:DirNode()
  237. call s:InfoExec('(dir)', 'Top')
  238. endfun
  239. fun! s:LastNode()
  240. if !exists('b:info_last_node')
  241. echohl ErrorMsg | echo 'No last node' | echohl None
  242. return
  243. endif
  244. call s:InfoExec(b:info_last_file, b:info_last_node, b:info_last_line)
  245. endfun
  246. fun! s:FollowLink()
  247. let current_line = getline('.')
  248. let link = matchstr(current_line, s:notePattern)
  249. if link == ''
  250. let link = matchstr(current_line, s:dirPattern)
  251. if link == ''
  252. let link = matchstr(current_line, s:menuPattern)
  253. if link == ''
  254. let link = matchstr(current_line, s:indexPattern)
  255. if link == ''
  256. echohl ErrorMsg | echo 'No link under cursor' | echohl None
  257. return
  258. endif
  259. let successPattern = s:indexPattern
  260. else
  261. let successPattern = s:menuPattern
  262. endif
  263. let file = b:info_file
  264. let node = substitute(link, successPattern, '\1', '')
  265. else
  266. let successPattern = s:dirPattern
  267. let file = substitute(link, successPattern, '\1', '')
  268. let node = 'Top'
  269. endif
  270. else
  271. " we got a `*note' link.
  272. let successPattern = s:notePattern
  273. let current_line = current_line.' '.getline(line('.') + 1)
  274. if exists('g:info_debug')
  275. echo 'current_line:' current_line
  276. endif
  277. let link_pattern = '\*[Nn]ote [^:.]\+: \([^.,]\+\)\%([,.]\|$\)'
  278. let link = matchstr(current_line, link_pattern)
  279. if link == ''
  280. let link_pattern = '\*[Nn]ote \([^:]\+\)\%(::\)'
  281. let link = matchstr(current_line, link_pattern)
  282. if link == ''
  283. echohl ErrorMsg | echo 'No link under cursor' | echohl None
  284. return
  285. endif
  286. endif
  287. let node = substitute(link, link_pattern, '\1', '')
  288. let successPattern = link_pattern
  289. let link_pattern = '^\(([^)]*)\)\=\s*\(.*\)'
  290. let link = matchstr(node, link_pattern)
  291. let file = substitute(link, link_pattern, '\1', '')
  292. let node = substitute(link, link_pattern, '\2', '')
  293. if file == ''
  294. let file = b:info_file
  295. endif
  296. if node == ''
  297. let node = 'Top'
  298. endif
  299. endif
  300. let link_start_pos = match(current_line, successPattern)
  301. let link_end_pos = matchend(current_line, successPattern)
  302. let cursor_pos = col('.')
  303. if cursor_pos <= link_start_pos || cursor_pos > link_end_pos
  304. echohl ErrorMsg | echo 'No link under cursor' | echohl None
  305. return
  306. endif
  307. if exists('g:info_debug')
  308. echo 'Link:' strpart(current_line, link_start_pos, link_end_pos - link_start_pos)
  309. echo 'File:' file 'Node:' node
  310. endif
  311. call s:InfoExec(file, node)
  312. endfun
  313. fun! s:NextRef()
  314. let link_pos = search('\('.s:dirPattern.'\|'.s:menuPattern.'\|'.s:notePattern.'\|'.s:indexPatternHL.'\)', 'w')
  315. if link_pos == 0
  316. echohl ErrorMsg | echo 'No hyperlinks' | echohl None
  317. else
  318. echo
  319. endif
  320. endfun
  321. fun! s:PrevNode()
  322. if exists('b:info_prev_node') && b:info_prev_node != ''
  323. \ && match(b:info_prev_node, '(.*)') == -1
  324. call s:InfoExec(b:info_file, b:info_prev_node)
  325. return 1
  326. else
  327. echohl ErrorMsg | echo 'This is the first node' | echohl None
  328. endif
  329. endfun
  330. fun! s:UpNode()
  331. if exists('b:info_up_node') && b:info_up_node != ''
  332. \ && match(b:info_up_node, '(.*)') == -1
  333. call s:InfoExec(b:info_file, b:info_up_node)
  334. return 1
  335. else
  336. echohl ErrorMsg | echo 'This is the top node' | echohl None
  337. endif
  338. endfun
  339. " FIXME: there is no way to correctly abort searching.
  340. " <CTRL-C> messes up the command window and stops at empty buffer.
  341. fun! s:Search()
  342. if !exists('s:info_search_string')
  343. let s:info_search_string = ''
  344. endif
  345. let new_search = input('Search all nodes: ', s:info_search_string)
  346. if new_search == ''
  347. return
  348. endif
  349. let s:info_search_string = new_search
  350. let start_file = b:info_file
  351. let start_node = b:info_node
  352. let start_line = line('.')
  353. while search(s:info_search_string, 'W') == 0
  354. if !exists('b:info_next_node') || b:info_next_node == ''
  355. \ || match(b:info_next_node, '(.*)') != -1
  356. silent! exe 'bwipe' escape(s:infoBufferName.start_file.start_node, '\ ')
  357. silent! call s:InfoExec(start_file, start_node, start_line, 'force')
  358. echohl ErrorMsg | echo "\rSearch pattern not found" | echohl None
  359. return
  360. endif
  361. echo "\rSearching ... ".b:info_file b:info_next_node
  362. let file = b:info_file
  363. let node = b:info_next_node
  364. silent bwipe
  365. silent call s:InfoExec(file, node, 2)
  366. endwhile
  367. endfun