Added Perforce plugin.
[profile.git] / .vim / plugin / info.vim
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
30 let s:infoCmd = 'info --output=-'
31 if has('win32')
32     let s:infoBufferName = '-Info- '
33 else
34     let s:infoBufferName = 'Info: '
35 endif
36 let s:dirPattern = '^\* [^:]*: \(([^)]*)\)'
37 let s:menuPattern = '^\* \([^:]*\)::'
38 let s:notePattern = '\*[Nn]ote\%(\s\|$\)'
39 let s:indexPattern = '^\* [^:]*:\s*\([^.]*\)\.$'
40 let s:indexPatternHL = '^\* [^:]*:\s\+[^(]'
41
42 command! -nargs=* Info  call s:Info(<f-args>)
43
44 fun! s:Info(...)
45     let file = "(dir)"
46     let node = "Top"
47     if a:0
48         let file = a:1
49         if file[0] != '('
50             let file = '('.file.')'
51         endif
52         if a:0 > 1
53             let node = a:2
54             let arg_idx = 3
55             while arg_idx <= a:0
56                 exe 'let node = node ." ". a:'.arg_idx
57                 let arg_idx = arg_idx + 1
58             endwhile
59         endif
60     endif
61
62     call s:InfoExec(file, node)
63     if winheight(2) != -1
64         exe 'resize' &helpheight
65     endif
66 endfun
67
68 fun! s:InfoExec(file, node, ...)
69     " a:0 != 0 means last node requested
70     if a:0
71         let line = a:1
72     else
73         let line = 1
74     endif
75     if a:0 == 0 && exists('b:info_file')
76         let last_file = b:info_file
77         let last_node = b:info_node
78         let last_line = line('.')
79     endif
80     let bufname = s:infoBufferName.a:file.a:node
81     if buflisted(bufname) && a:0 < 2
82         if &ft == 'info'
83             silent exe 'b!' escape(bufname, '\ ')
84         else 
85             silent exe 'sb' escape(bufname, '\ ')
86         endif
87     else
88         if &ft == 'info'
89             let command = 'e!'
90         else
91             let command = 'new'
92         endif
93         silent! exe command "+exe'setlocal''modifiable''noswapfile''buftype=nofile''bufhidden=delete'" escape(bufname, '\ ')
94         setf info
95
96         let cmd = s:infoCmd." '".a:file.a:node."' 2>/dev/null"
97
98         " handle shell redirection portable
99         if $OS !~? 'Windows'
100             let save_shell = &shell
101             set shell=/bin/sh
102         endif
103         let save_shellredir = &shellredir
104         set shellredir=>
105         exe "silent 0r!".cmd
106         let &shellredir = save_shellredir
107         if $OS !~? 'Windows'
108             let &shell = save_shell
109         endif
110
111         call s:InfoBufferInit()
112     endif
113     let b:info_file = a:file
114     let b:info_node = a:node
115     if exists('last_file')
116         let b:info_last_file = last_file
117         let b:info_last_node = last_node
118         let b:info_last_line = last_line
119     endif
120     setlocal nomodifiable
121     if s:InfoFirstLine()
122         exe line
123     else
124         echohl ErrorMsg | echo 'Info failed (node not found)' | echohl None
125     endif
126 endfun
127
128 fun! s:InfoBufferInit()
129
130     " remove all insert mode abbreviations
131     iabc <buffer>
132
133     if has("syntax") && exists("g:syntax_on")
134         syn case match
135         syn match infoMenuTitle         /^\* Menu:/hs=s+2,he=e-1
136         syn match infoTitle             /^[A-Z][0-9A-Za-z `',/&]\{,43}\([a-z']\|[A-Z]\{2}\)$/
137         syn match infoTitle             /^[-=*]\{,45}$/
138         syn match infoString            /`[^`]*'/
139         exec 'syn match infoLink        /'.s:menuPattern.'/hs=s+2'
140         exec 'syn match infoLink        /'.s:dirPattern.'/hs=s+2'
141         exec 'syn match infoLink        /'.s:indexPatternHL.'/hs=s+2,he=e-2'
142         syn region infoLink             start=/\*[Nn]ote/ end=/\(::\|[.,]\)/
143
144         if !exists("g:did_info_syntax_inits")
145             let g:did_info_syntax_inits = 1
146             hi def link infoMenuTitle   Title
147             hi def link infoTitle       Comment
148             hi def link infoLink        Directory
149             hi def link infoString      String
150         endif
151     endif
152
153     " FIXME: <h> is move cursor left
154     noremap <buffer> h          :call <SID>Help()<cr>
155     noremap <buffer> <CR>       :call <SID>FollowLink()<cr>
156     noremap <buffer> <C-]>      :call <SID>FollowLink()<cr>
157     " FIXME: <l> is move cursor right
158 "    noremap <buffer> l         :call <SID>LastNode()<cr>
159     noremap <buffer> ;          :call <SID>LastNode()<cr>
160     noremap <buffer> <C-T>      :call <SID>LastNode()<cr>
161     noremap <buffer> <C-S>      /
162     " FIXME: <n> is go to next match
163 "    noremap <buffer> n         :call <SID>NextNode()<cr>
164     noremap <buffer> .          :call <SID>NextNode()<cr>
165     noremap <buffer> p          :call <SID>PrevNode()<cr>
166     noremap <buffer> >          :call <SID>NextNode()<cr>
167     noremap <buffer> <          :call <SID>PrevNode()<cr>
168     noremap <buffer> u          :call <SID>UpNode()<cr>
169     noremap <buffer> t          :call <SID>TopNode()<cr>
170     noremap <buffer> d          :call <SID>DirNode()<cr>
171     noremap <buffer> s          :call <SID>Search()<cr>
172     noremap <buffer> <TAB>      :call <SID>NextRef()<cr>
173     nnoremap <buffer> q         :q!<cr>
174     noremap <buffer> <Space>    <C-F>
175     noremap <buffer> <Backspace> <C-B>
176
177     runtime info-local.vim
178 endfun
179
180 fun! s:Help()
181     echohl Title
182     echo 'Info browser keys'
183     echo '-----------------'
184     echohl None
185     echo '<Space>               Scroll forward (page down).'
186     echo '<Backspace>   Scroll backward (page up).'
187     echo '<Tab>         Move cursor to next hyperlink within this node.'
188     echo '<Enter>,<C-]> Follow hyperlink under cursor.'
189     echo ';,<C-T>               Return to last seen node.'
190     echo '.,>           Move to the "next" node of this node.'
191     echo 'p,<           Move to the "previous" node of this node.'
192     echo 'u             Move "up" from this node.'
193     echo 'd             Move to "directory" node.'
194     echo 't             Move to the Top node.'
195     echo '<C-S>         Search forward within current node only.'
196     echo 's             Search forward through all nodes for a specified string.'
197     echo 'q             Quit browser.'
198     echohl SpecialKey
199     echo 'Note: "," means "or"'
200     echohl None
201 endfun
202
203 fun! s:InfoFirstLine()
204     let b:info_next_node = ''
205     let b:info_prev_node = ''
206     let b:info_up_node = ''
207     let line = getline(1)
208     let node_offset = matchend(line, '^File: [^,        ]*')
209     if node_offset == -1
210         return 0
211     endif
212     let file = strpart(line, 6, node_offset-6)
213     if file == 'dir'
214         return 1
215     endif
216 "    let file = substitute(file, '\(.*\)\.info\(\.gz\)\=', '\1', '')
217     let b:info_next_node = s:GetSubmatch( line, '\s\+Next: \([^,]*\),')
218     let b:info_prev_node = s:GetSubmatch( line, '\s\+Prev: \([^,]*\),')
219     let b:info_up_node = s:GetSubmatch( line, '\s\+Up: \(.*\)')
220     return 1
221 endfun
222
223 fun! s:GetSubmatch(string, pattern)
224     let matched = matchstr(a:string, a:pattern)
225     if matched != ''
226         let matched = substitute(matched, a:pattern, '\1', '')
227     endif
228     return matched
229 endfun
230
231 fun! s:NextNode()
232     if exists('b:info_next_node') && b:info_next_node != ''
233         \ && match(b:info_next_node, '(.*)') == -1
234         call s:InfoExec(b:info_file, b:info_next_node)
235         return 1
236     else
237         echohl ErrorMsg | echo 'This is the last node' | echohl None
238     endif
239 endfun
240
241 fun! s:TopNode()
242     if b:info_node == 'Top'
243         if b:info_file == '(dir)'
244             echohl ErrorMsg | echo 'Already at top node' | echohl None
245             return
246         endif
247         let file = '(dir)'
248         let node = b:info_node
249     else
250         let file = b:info_file
251         let node = 'Top'
252     endif
253     call s:InfoExec(file, node)
254 endfun
255
256 fun! s:DirNode()
257     call s:InfoExec('(dir)', 'Top')
258 endfun
259
260 fun! s:LastNode()
261     if !exists('b:info_last_node')
262         echohl ErrorMsg | echo 'No last node' | echohl None
263         return
264     endif
265     call s:InfoExec(b:info_last_file, b:info_last_node, b:info_last_line)
266 endfun
267
268 fun! s:FollowLink()
269     let current_line = getline('.')
270     let link = matchstr(current_line, s:notePattern)
271     if link == ''
272         let link = matchstr(current_line, s:dirPattern)
273         if link == ''
274             let link = matchstr(current_line, s:menuPattern)
275             if link == ''
276                 let link = matchstr(current_line, s:indexPattern)
277                 if link == ''
278                     echohl ErrorMsg | echo 'No link under cursor' | echohl None
279                     return
280                 endif
281                 let successPattern = s:indexPattern
282             else
283                 let successPattern = s:menuPattern
284             endif
285             let file = b:info_file
286             let node = substitute(link, successPattern, '\1', '')
287         else
288             let successPattern = s:dirPattern
289             let file = substitute(link, successPattern, '\1', '')
290             let node = 'Top'
291         endif
292     else
293         " we got a `*note' link.
294         let successPattern = s:notePattern
295         let current_line = current_line.' '.getline(line('.') + 1)
296
297         if exists('g:info_debug')
298             echo 'current_line:' current_line
299         endif
300         
301         let link_pattern = '\*[Nn]ote [^:.]\+: \([^.,]\+\)\%([,.]\|$\)'
302         let link = matchstr(current_line, link_pattern)
303         if link == ''
304             let link_pattern = '\*[Nn]ote \([^:]\+\)\%(::\)'
305             let link = matchstr(current_line, link_pattern)
306             if link == ''
307                 echohl ErrorMsg | echo 'No link under cursor' | echohl None
308                 return
309             endif
310         endif
311         let node = substitute(link, link_pattern, '\1', '')
312         let successPattern = link_pattern
313
314         let link_pattern = '^\(([^)]*)\)\=\s*\(.*\)'
315         let link = matchstr(node, link_pattern)
316         let file = substitute(link, link_pattern, '\1', '')
317         let node = substitute(link, link_pattern, '\2', '')
318         if file == ''
319             let file = b:info_file
320         endif
321         if node == ''
322             let node = 'Top'
323         endif
324     endif
325     let link_start_pos = match(current_line, successPattern)
326     let link_end_pos = matchend(current_line, successPattern)
327     let cursor_pos = col('.')
328     if cursor_pos <= link_start_pos || cursor_pos > link_end_pos
329         echohl ErrorMsg | echo 'No link under cursor' | echohl None
330         return
331     endif
332     if exists('g:info_debug')
333         echo 'Link:' strpart(current_line, link_start_pos, link_end_pos - link_start_pos)
334         echo 'File:' file 'Node:' node
335     endif
336     call s:InfoExec(file, node)
337 endfun
338
339 fun! s:NextRef()
340     let link_pos = search('\('.s:dirPattern.'\|'.s:menuPattern.'\|'.s:notePattern.'\|'.s:indexPatternHL.'\)', 'w')
341     if link_pos == 0
342         echohl ErrorMsg | echo 'No hyperlinks' | echohl None
343     else
344         echo
345     endif
346 endfun
347
348 fun! s:PrevNode()
349     if exists('b:info_prev_node') && b:info_prev_node != ''
350         \ && match(b:info_prev_node, '(.*)') == -1
351         call s:InfoExec(b:info_file, b:info_prev_node)
352         return 1
353     else
354         echohl ErrorMsg | echo 'This is the first node' | echohl None
355     endif
356 endfun
357
358 fun! s:UpNode()
359     if exists('b:info_up_node') && b:info_up_node != ''
360         \ && match(b:info_up_node, '(.*)') == -1
361         call s:InfoExec(b:info_file, b:info_up_node)
362         return 1
363     else
364         echohl ErrorMsg | echo 'This is the top node' | echohl None
365     endif
366 endfun
367
368 " FIXME: there is no way to correctly abort searching.
369 " <CTRL-C> messes up the command window and stops at empty buffer.
370 fun! s:Search()
371     if !exists('s:info_search_string')
372         let s:info_search_string = ''
373     endif
374     let new_search = input('Search all nodes: ', s:info_search_string)
375     if new_search == ''
376         return
377     endif
378     let s:info_search_string = new_search
379     let start_file = b:info_file
380     let start_node = b:info_node
381     let start_line = line('.')
382     while search(s:info_search_string, 'W') == 0
383         if !exists('b:info_next_node') || b:info_next_node == ''
384             \ || match(b:info_next_node, '(.*)') != -1
385             silent! exe 'bwipe' escape(s:infoBufferName.start_file.start_node, '\ ')
386             silent! call s:InfoExec(start_file, start_node, start_line, 'force')
387             echohl ErrorMsg | echo "\rSearch pattern not found" | echohl None
388             return
389         endif
390         echo "\rSearching ... ".b:info_file b:info_next_node
391         let file = b:info_file
392         let node = b:info_next_node
393         silent bwipe
394         silent call s:InfoExec(file, node, 2)
395     endwhile
396 endfun