patch 9.1.0836: The vimtutor can be improved
Problem: the vimtutor can be improved
Solution: port and include the interactive vimtutor plugin from Neovim
(by Felipe Morales) (Yegappan Lakshmanan)
closes: #6414
Signed-off-by: Christian Brabandt <cb@256bit.org>
Signed-off-by: Yegappan Lakshmanan <yegappan@yahoo.com>
diff --git a/runtime/autoload/tutor.vim b/runtime/autoload/tutor.vim
new file mode 100644
index 0000000..3265fdd
--- /dev/null
+++ b/runtime/autoload/tutor.vim
@@ -0,0 +1,219 @@
+" vim: fdm=marker et ts=4 sw=4
+
+" Setup: {{{1
+function! tutor#SetupVim()
+ if !exists('g:did_load_ftplugin') || g:did_load_ftplugin != 1
+ filetype plugin on
+ endif
+ if has('syntax')
+ if !exists('g:syntax_on') || g:syntax_on == 0
+ syntax on
+ endif
+ endif
+endfunction
+
+" Loads metadata file, if available
+function! tutor#LoadMetadata()
+ let b:tutor_metadata = json_decode(join(readfile(expand('%').'.json'), "\n"))
+endfunction
+
+" Mappings: {{{1
+
+function! tutor#SetNormalMappings()
+ nnoremap <silent> <buffer> <CR> :call tutor#FollowLink(0)<cr>
+ nnoremap <silent> <buffer> <2-LeftMouse> :call tutor#MouseDoubleClick()<cr>
+ nnoremap <buffer> >> :call tutor#InjectCommand()<cr>
+endfunction
+
+function! tutor#MouseDoubleClick()
+ if foldclosed(line('.')) > -1
+ normal! zo
+ else
+ if match(getline('.'), '^#\{1,} ') > -1
+ silent normal! zc
+ else
+ call tutor#FollowLink(0)
+ endif
+ endif
+endfunction
+
+function! tutor#InjectCommand()
+ let l:cmd = substitute(getline('.'), '^\s*', '', '')
+ exe l:cmd
+ redraw | echohl WarningMsg | echon "tutor: ran" | echohl None | echon " " | echohl Statement | echon l:cmd
+endfunction
+
+function! tutor#FollowLink(force)
+ let l:stack_s = join(map(synstack(line('.'), col('.')), 'synIDattr(v:val, "name")'), '')
+ if l:stack_s =~# 'tutorLink'
+ let l:link_start = searchpairpos('\[', '', ')', 'nbcW')
+ let l:link_end = searchpairpos('\[', '', ')', 'ncW')
+ if l:link_start[0] == l:link_end[0]
+ let l:linkData = getline(l:link_start[0])[l:link_start[1]-1:l:link_end[1]-1]
+ else
+ return
+ endif
+ let l:target = matchstr(l:linkData, '(\@<=.*)\@=')
+ if a:force != 1 && match(l:target, '\*.\+\*') > -1
+ call cursor(l:link_start[0], l:link_end[1])
+ call search(l:target, '')
+ normal! ^
+ elseif a:force != 1 && match(l:target, '^@tutor:') > -1
+ let l:tutor = matchstr(l:target, '@tutor:\zs.*')
+ exe "Tutor ".l:tutor
+ else
+ exe "help ".l:target
+ endif
+ endif
+endfunction
+
+" Folding And Info: {{{1
+
+function! tutor#TutorFolds()
+ if getline(v:lnum) =~# '^#\{1,6}'
+ return ">". len(matchstr(getline(v:lnum), '^#\{1,6}'))
+ else
+ return "="
+ endif
+endfunction
+
+" Marks: {{{1
+
+function! tutor#ApplyMarks()
+ hi! link tutorExpect Special
+ if exists('b:tutor_metadata') && has_key(b:tutor_metadata, 'expect')
+ let b:tutor_sign_id = 1
+ for expct in keys(b:tutor_metadata['expect'])
+ let lnum = eval(expct)
+ call matchaddpos('tutorExpect', [lnum])
+ call tutor#CheckLine(lnum)
+ endfor
+ endif
+endfunction
+
+function! tutor#ApplyMarksOnChanged()
+ if exists('b:tutor_metadata') && has_key(b:tutor_metadata, 'expect')
+ let lnum = line('.')
+ if index(keys(b:tutor_metadata['expect']), string(lnum)) > -1
+ call tutor#CheckLine(lnum)
+ endif
+ endif
+endfunction
+
+function! tutor#CheckLine(line)
+ if exists('b:tutor_metadata') && has_key(b:tutor_metadata, 'expect')
+ let bufn = bufnr('%')
+ let ctext = getline(a:line)
+ if b:tutor_metadata['expect'][string(a:line)] == -1 || ctext ==# b:tutor_metadata['expect'][string(a:line)]
+ exe "sign place ".b:tutor_sign_id." line=".a:line." name=tutorok buffer=".bufn
+ else
+ exe "sign place ".b:tutor_sign_id." line=".a:line." name=tutorbad buffer=".bufn
+ endif
+ let b:tutor_sign_id+=1
+ endif
+endfunction
+
+" Tutor Cmd: {{{1
+
+function! s:Locale()
+ if exists('v:lang') && v:lang =~ '\a\a'
+ let l:lang = v:lang
+ elseif $LC_ALL =~ '\a\a'
+ let l:lang = $LC_ALL
+ elseif $LANG =~ '\a\a'
+ let l:lang = $LANG
+ else
+ let l:lang = 'en_US'
+ endif
+ return split(l:lang, '_')
+endfunction
+
+function! s:GlobPath(lp, pat)
+ if version >= 704 && has('patch279')
+ return globpath(a:lp, a:pat, 1, 1)
+ else
+ return split(globpath(a:lp, a:pat, 1), '\n')
+ endif
+endfunction
+
+function! s:Sort(a, b)
+ let mod_a = fnamemodify(a:a, ':t')
+ let mod_b = fnamemodify(a:b, ':t')
+ if mod_a == mod_b
+ let retval = 0
+ elseif mod_a > mod_b
+ if match(mod_a, '^vim-') > -1 && match(mod_b, '^vim-') == -1
+ let retval = -1
+ else
+ let retval = 1
+ endif
+ else
+ if match(mod_b, '^vim-') > -1 && match(mod_a, '^vim-') == -1
+ let retval = 1
+ else
+ let retval = -1
+ endif
+ endif
+ return retval
+endfunction
+
+function! s:GlobTutorials(name)
+ " search for tutorials:
+ " 1. non-localized
+ let l:tutors = s:GlobPath(&rtp, 'tutor/'.a:name.'.tutor')
+ " 2. localized for current locale
+ let l:locale_tutors = s:GlobPath(&rtp, 'tutor/'.s:Locale()[0].'/'.a:name.'.tutor')
+ " 3. fallback to 'en'
+ if len(l:locale_tutors) == 0
+ let l:locale_tutors = s:GlobPath(&rtp, 'tutor/en/'.a:name.'.tutor')
+ endif
+ call extend(l:tutors, l:locale_tutors)
+ return uniq(sort(l:tutors, 's:Sort'), 's:Sort')
+endfunction
+
+function! tutor#TutorCmd(tutor_name)
+ if match(a:tutor_name, '[[:space:]]') > 0
+ echom "Only one argument accepted (check spaces)"
+ return
+ endif
+
+ if a:tutor_name == ''
+ let l:tutor_name = 'vim-01-beginner.tutor'
+ else
+ let l:tutor_name = a:tutor_name
+ endif
+
+ if match(l:tutor_name, '\.tutor$') > 0
+ let l:tutor_name = fnamemodify(l:tutor_name, ':r')
+ endif
+
+ let l:tutors = s:GlobTutorials(l:tutor_name)
+
+ if len(l:tutors) == 0
+ echom "No tutorial with that name found"
+ return
+ endif
+
+ if len(l:tutors) == 1
+ let l:to_open = l:tutors[0]
+ else
+ let l:idx = 0
+ let l:candidates = ['Several tutorials with that name found. Select one:']
+ for candidate in map(copy(l:tutors),
+ \'fnamemodify(v:val, ":h:h:t")."/".s:Locale()[0]."/".fnamemodify(v:val, ":t")')
+ let l:idx += 1
+ call add(l:candidates, l:idx.'. '.candidate)
+ endfor
+ let l:tutor_to_open = inputlist(l:candidates)
+ let l:to_open = l:tutors[l:tutor_to_open-1]
+ endif
+
+ call tutor#SetupVim()
+ exe "edit ".l:to_open
+endfunction
+
+function! tutor#TutorCmdComplete(lead,line,pos)
+ let l:tutors = s:GlobTutorials('*')
+ let l:names = uniq(sort(map(l:tutors, 'fnamemodify(v:val, ":t:r")'), 's:Sort'))
+ return join(l:names, "\n")
+endfunction