Commit 94a47bec authored by HiPhish's avatar HiPhish

Change how :Follow works without arguments

':Follow' works now the same as ':Menu'. 'K' has been remapped to its
own function. We also now handle dead links gracefully.
parent 554be6cd
......@@ -77,10 +77,13 @@ You can follow cross-references using the `:Follow` command:
.. code-block:: vim
" Display all cross references in location list
:Follow
" Follow a named cross-reference
:Follow Name of the reference
" Follow reference under cursor (works for any kind of reference)
:Follow
Use the `K` key in normal mode to follow the reference under the cursor, works
for both menu entries and cross references.
Navigation
......
......@@ -213,15 +213,15 @@ COMMANDS *info-commands*
With no argument open the location list populated with the menu entries
for the current node. With an argument jump to that node. The argument
does not have to be a perfect match, it only needs to match at the head
case-insensitively.
does not have to be a perfect match, it only needs to match at the head.
------------------------------------------------------------------------------
:Follow [{xRef}] *info-:Follow*
Follow the given cross-reference. Without argument follow the reference
under the cursor. If there is none only a message will be printed.
Follow the given cross-reference. Without argument the location list is
populated with all cross references in the current node. The argument does
not have to be a perfect mtach, it only needs to match at the head.
------------------------------------------------------------------------------
......
......@@ -15,15 +15,14 @@ Info document is a regular buffer in that regard. This applies all other Vim
modes as well.
Let's move on to the first lesson. You can either move the cursor down onto
the left-hand item of the first menu entry and press ‘K’ or use the ‘:Menu’
command. If you use the command you must give the name of the menu entry as an
argument. You can use the ‘Tab’ key to complete the argument if you don't feel
like typing everything. Finally, you can also execute the ‘:InfoNext’ command.
the left-hand item of the first menu entry and press ‘K’. You will soon learn
how to use menus properly.
* Menu:
* Getting Started:: Getting started using the Info.vim reader.
* Advanced:: Advanced Info.vim commands.
* Info: (info)Top. The official Info manual.

File: info.vim.info, Node: Getting Started, Next: Advanced, Prev: Top, Up: Top
......@@ -33,7 +32,7 @@ File: info.vim.info, Node: Getting Started, Next: Advanced, Prev: Top, Up: T
The first part of this Info tutorial will briefly explain how Info documents
are structured and how to navigate them. Repeat what you did previously to get
here to move on (except for ‘:InfoNext’, you cannot use that one right now).
here to move on.
* Menu:
......@@ -65,7 +64,7 @@ the information about the current node of the file. You can also access this
information through scripting (*note Advanced::).
Now it is time to learn how to navigate between nodes. Execute ‘:InfoNext’
one more time.
to move to the next node.

File: info.vim.info, Node: Node-Navigation, Next: Menu-Navigation, Prev: Info-Structure, Up: Getting Started
......@@ -74,7 +73,7 @@ File: info.vim.info, Node: Node-Navigation, Next: Menu-Navigation, Prev: Info
========================================
There are two ways of navigating nodes: via commands or mappings. Commands are
one option that works out of the box, so that is what we will be using
the one option that works out of the box, so that is what we will be using
throughout this tutorial. See the manual of Info.vim for the mappings.
The naming convention for the commands is ‘:InfoWhich’ where ‘Which’ is
......
......@@ -34,12 +34,6 @@ setlocal shiftwidth=8
nnoremap <buffer> g? :call <SID>help()<CR>
if &buftype =~? 'nofile'
nnoremap <silent> <buffer> K :Follow<CR>
nnoremap <silent> <buffer> <C-]> :Follow<CR>
endif
" Echo a quick instruction list
function! s:help()
echomsg 'Execute '':Info info.vim'' for an interactive tutorial. The following'
......
......@@ -123,6 +123,14 @@ augroup InfoFiletype
autocmd FileType info command! -buffer InfoNext call <SID>next()
autocmd FileType info command! -buffer InfoPrev call <SID>prev()
" Look up the reference under the cursor (for cross-references and menus)
autocmd FileType info if &buftype =~? 'nofile' |
\nnoremap <silent> <buffer> K :call <SID>xRefUnderCursor()<CR> |
\endif
autocmd FileType info if &buftype =~? 'nofile' |
\nnoremap <silent> <buffer> <C-]> K |
\endif
autocmd BufReadCmd info://* call <SID>readReference(<SID>decodeURI(expand('<amatch>')))
augroup END
......@@ -349,24 +357,17 @@ function s:menuPrompt()
endfunction
function! s:menu(pattern)
if a:pattern ==# ''
call setloclist(0, [], ' ', 'Menu')
for l:item in b:info['Menu']
if exists('l:item[''line'']')
let l:line = l:item['line']
else
let l:line = 1
endif
let l:uri = s:encodeURI(l:item)
laddexpr l:uri.'\|'.l:line.'\| '.l:item['Name']
endfor
lopen
if !exists('b:info[''Menu'']')
echohl ErrorMsg
echo 'No menu in this node.'
echohl NONE
return
endif
if a:pattern ==# ''
return s:populateLocList('Menu', b:info['Menu'])
endif
let l:entry = s:findReferenceInList(a:pattern, b:info['Menu'])
if empty(l:entry)
......@@ -376,6 +377,10 @@ function! s:menu(pattern)
return
endif
if !s:verifyReference(l:entry)
return
endif
let l:uri = s:encodeURI(l:entry)
call s:executeURI('silent edit ', l:uri)
endfunction
......@@ -383,14 +388,8 @@ endfunction
" Build up a list of menu entries in a node.
function! s:buildMenu()
" This function will be called lazily when we need a menu. Don't rebuild
" it is one already exists.
if exists('b:info[''Menu'']')
return
endif
let l:save_cursor = getcurpos()
let b:info['Menu'] = []
let l:menu = []
let l:menuLine = search('\v^\* [Mm]enu\:')
if l:menuLine == 0
......@@ -401,10 +400,14 @@ function! s:buildMenu()
" beginning of the file or we will be stuck in an infinite loop.
let l:entryLine = search('\v^\*[^:]+\:', 'W')
while l:entryLine != 0
call add(b:info['Menu'], s:decodeRefString(getline(l:entryLine)))
call add(l:menu, s:decodeRefString(getline(l:entryLine)))
let l:entryLine = search('\v^\*[^:]+\:', 'W')
endwhile
if !empty(l:menu)
let b:info['Menu'] = l:menu
endif
call setpos('.', l:save_cursor)
endfunction
......@@ -429,29 +432,37 @@ endfunction
" Follow the cross-reference under the cursor.
function! s:follow(pattern)
if !exists('b:info[''XRefs'']')
echohl ErrorMsg
echo 'No cross reference in this node.'
echohl NONE
return
endif
if a:pattern ==# ''
let l:xRef = s:xRefUnderCursor()
else
let l:xRef = s:findReferenceInList(a:pattern, b:info['XRefs'])
return s:populateLocList('Cross references', b:info['XRefs'])
return
endif
let l:xRef = s:findReferenceInList(a:pattern, b:info['XRefs'])
if empty(l:xRef)
echohl ErrorMsg
echo 'No cross reference under cursor.'
echo 'No cross reference matches '''.a:pattern.'''.'
echohl NONE
return
endif
if !s:verifyReference(l:xRef)
return
endif
let l:uri = s:encodeURI(l:xRef)
call s:executeURI('silent edit ', l:uri)
endfunction
function! s:collectXRefs()
if exists('b:info[''XRefs'']')
return
endif
" Pattern to search for (will match over line breaks)
let l:pattern = '\v\*[Nn]ote\_s*\_[^:]+\:(\_s*\_[^:.,]+[:.,]|\:)'
......@@ -475,13 +486,16 @@ function! s:collectXRefs()
endfor
let b:info['XRefs'] = l:xRefs
if !empty(l:xRefs)
let b:info['XRefs'] = l:xRefs
endif
call setpos('.', l:save_cursor)
endfunction
" Parse the current line for the existence of a reference element.
function! s:xRefUnderCursor()
let l:referencePattern = '\v\*([Nn]ote\s+)?[^:]+\:(\:|[^.]+\.)'
let l:referencePattern = '\v\*([Nn]ote\s+)?\_[^:]+\:(\:|\_[^.,]+[.,])'
" Test-cases for the reference pattern
" *Note Directory: (dir)Top.
" *Note Directory::
......@@ -499,29 +513,33 @@ function! s:xRefUnderCursor()
let l:line .= getline('.' ) . ' '
let l:line .= getline(line('.') + 1)
" The match and matchend are 0-indexed, so we subtract one
let l:col = len(getline(line('.') - 1)) + col('.') - 1
" +1 because of space we added
let l:col = len(getline(line('.') - 1)) + col('.') + 1
let l:start = 0
while l:col >= l:start
let l:start = match(l:line, l:referencePattern)
" match is zero-indexed, so add one
let l:start = match(l:line, l:referencePattern) + 1
let l:end = matchend(l:line, l:referencePattern)
if l:start < 0
if l:start < 0 || l:end < 0
break
endif
if l:col < l:end
let l:xRefString = matchstr(l:line, l:referencePattern)
let l:xRef = s:decodeRefString(l:xRefString)
return l:xRef
call s:executeURI('silent edit ', s:encodeURI(l:xRef))
return
endif
let l:line = l:line[l:end :]
let l:col -= l:end
endwhile
return {}
echohl ErrorMsg
echo 'No cross reference under cursor.'
echohl NONE
endfunction
......@@ -723,6 +741,24 @@ function! s:completePrompt(ArgLead, CmdLine, CursorPos, list)
return l:candidates
endfunction
" Populate the location list with items from 'from'
function! s:populateLocList(title, from)
call setloclist(0, [], '', a:title)
for l:item in a:from
if exists('l:item[''line'']')
let l:line = l:item['line']
else
let l:line = 1
endif
let l:uri = s:encodeURI(l:item)
laddexpr l:uri.'\|'.l:line.'\| '.l:item['Name']
endfor
lopen
endfunction
" Parse a reference string into a reference object.
function! s:decodeRefString(string)
" Strip away the leading cruft first: '* ' and '*Note '
......
......@@ -11,6 +11,13 @@ After (Restore the original info binary):
let g:infoprg = g:old_infoprg
Execute (Absence of the 'Menu' key):
silent Info test
Then:
Assert !exists('b:info[''Menu'']')
Execute (Existence of the 'Menu' key):
silent Info test Menu
......@@ -27,7 +34,8 @@ Then:
\ {'File': 'test', 'Name': 'Foo', 'Node': 'Bar'},
\ {'File': 'bar', 'Name': 'Foo', 'Node': 'Baz'},
\ {'File': 'test', 'Name': 'Foo', 'Node': 'Bar'},
\ {'File': 'test', 'Name': 'Foo', 'Node': 'Bar'}],
\ {'File': 'test', 'Name': 'Foo', 'Node': 'Bar'},
\ {'File': 'test', 'Name': 'Nul', 'Node': 'Nil'}],
\ b:info['Menu']
quit
......@@ -42,6 +50,7 @@ Expect qf (Location list contains menu entries):
info://bar/Baz/|1| Foo
info://test/Bar/|1| Foo
info://test/Bar/|1| Foo
info://test/Nil/|1| Nul
Execute (The ':Menu' command with argument):
......@@ -51,3 +60,22 @@ Execute (The ':Menu' command with argument):
Then (We have successfully opened another node):
AssertEqual 'info://test/Foo/', expand('%')
quit
Execute (Following reference under cursor):
silent Info test Menu
call cursor(9, 1)
normal K
Then:
AssertEqual 'info://test/Foo/', expand('%')
quit
Execute (Dead link):
silent Info test Menu
silent Menu Nul
Then:
AssertEqual 'info://test/Menu/', expand('%')
quit
......@@ -28,4 +28,12 @@ done
# The $(dirname $0) is save only as long as the script is invoked with its full
# path, which will be true as long as the test cases use 'g:vader_file' to
# construct the path.
cat "$(dirname "$0")/mock/$FILE.$NODE.info"
TARGET="$(dirname "$0")/mock/$FILE.$NODE.info"
if [ ! -f "$TARGET" ]; then
# The >&2 means "redirect address of FD 1 to FD 2"
>&2 echo "info: '$FILE': No such file or directory"
# If the node was not found: echo "info: Cannot find node '$NODE'."
exit 1
fi
cat "$TARGET"
File: test, Node: Foo Bar, Prev: Foo, Up: Top
......@@ -3,3 +3,5 @@ File: test, Node: X-Ref, Prev: Foo, Up: Top
*Note Foo:: is a reference with ‘N’, but *note Foo:: has a ‘n’. We can have
separate name and node like *note Foo: Bar, and even line breaks as *note Foo
Bar::; valid separators are comma and dot *note Foo: Bar.
The reference *note Nul: Nil, is a dead link.
......@@ -12,3 +12,4 @@ character is a valid separator.
There can be non-entries in between.
* Foo: Bar. Dot separator
* Foo: Bar, Comma separator
* Nul: Nil Non-existing node
......@@ -11,6 +11,13 @@ After (Restore the original info binary):
let g:infoprg = g:old_infoprg
Execute (Absence of the 'XRefs' key):
silent Info test
Then:
Assert !exists('b:info[''XRefs'']')
Execute (Existence of the 'Menu' key):
silent Info test X-Ref
......@@ -28,7 +35,8 @@ Then:
\ {'File': 'test', 'Name': 'Foo', 'Node': 'Foo'},
\ {'File': 'test', 'Name': 'Foo', 'Node': 'Bar'},
\ {'File': 'test', 'Name': 'Foo Bar', 'Node': 'Foo Bar'},
\ {'File': 'test', 'Name': 'Foo', 'Node': 'Bar'}],
\ {'File': 'test', 'Name': 'Foo', 'Node': 'Bar'},
\ {'File': 'test', 'Name': 'Nul', 'Node': 'Nil'}],
\ b:info['XRefs']
quit
......@@ -36,6 +44,44 @@ Execute (The ':Follow' command with argument):
silent Info test X-Ref
silent Follow Foo
Then (We have successfully opened another node):
Then:
AssertEqual 'info://test/Foo/', expand('%')
quit
Execute (Following the reference under the cursor):
silent Info test X-Ref
call cursor(3, 1)
normal K
Then:
AssertEqual 'info://test/Foo/', expand('%')
quit
Execute (Not following non-existing reference under the cursor):
silent Info test
normal K
Then (We have stayed in the same node):
AssertEqual 'info://test/Top/', expand('%')
quit
Execute (Following reference under cursor with line break):
silent Info test X-Ref
call cursor(5, 1)
normal K
Then:
AssertEqual 'info://test/Foo%20Bar/', expand('%')
quit
Execute (Dead link):
silent Info test X-Ref
silent Follow Nul
Then:
AssertEqual 'info://test/X-Ref/', expand('%')
quit
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment