tutor.vim 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. " vim: fdm=marker et ts=4 sw=4
  2. " Setup: {{{1
  3. function! tutor#SetupVim()
  4. if !exists('g:did_load_ftplugin') || g:did_load_ftplugin != 1
  5. filetype plugin on
  6. endif
  7. if has('syntax')
  8. if !exists('g:syntax_on') || g:syntax_on == 0
  9. syntax on
  10. endif
  11. endif
  12. endfunction
  13. " Loads metadata file, if available
  14. function! tutor#LoadMetadata()
  15. let b:tutor_metadata = json_decode(join(readfile(expand('%').'.json'), "\n"))
  16. endfunction
  17. " Mappings: {{{1
  18. function! tutor#SetNormalMappings()
  19. nnoremap <silent> <buffer> <CR> :call tutor#FollowLink(0)<cr>
  20. nnoremap <silent> <buffer> <2-LeftMouse> :call tutor#MouseDoubleClick()<cr>
  21. nnoremap <buffer> >> :call tutor#InjectCommand()<cr>
  22. endfunction
  23. function! tutor#MouseDoubleClick()
  24. if foldclosed(line('.')) > -1
  25. normal! zo
  26. else
  27. if match(getline('.'), '^#\{1,} ') > -1 && foldlevel(line('.')) > 0
  28. silent normal! zc
  29. else
  30. call tutor#FollowLink(0)
  31. endif
  32. endif
  33. endfunction
  34. function! tutor#InjectCommand()
  35. let l:cmd = substitute(getline('.'), '^\s*', '', '')
  36. exe l:cmd
  37. redraw | echohl WarningMsg | echon "tutor: ran" | echohl None | echon " " | echohl Statement | echon l:cmd
  38. endfunction
  39. function! tutor#FollowLink(force)
  40. let l:stack_s = join(map(synstack(line('.'), col('.')), 'synIDattr(v:val, "name")'), '')
  41. if l:stack_s =~# 'tutorLink'
  42. let l:link_start = searchpairpos('\[', '', ')', 'nbcW')
  43. let l:link_end = searchpairpos('\[', '', ')', 'ncW')
  44. if l:link_start[0] == l:link_end[0]
  45. let l:linkData = getline(l:link_start[0])[l:link_start[1]-1:l:link_end[1]-1]
  46. else
  47. return
  48. endif
  49. let l:target = matchstr(l:linkData, '(\@<=.*)\@=')
  50. if a:force != 1 && match(l:target, '\*.\+\*') > -1
  51. call cursor(l:link_start[0], l:link_end[1])
  52. call search(l:target, '')
  53. normal! ^
  54. elseif a:force != 1 && match(l:target, '^@tutor:') > -1
  55. let l:tutor = matchstr(l:target, '@tutor:\zs.*')
  56. exe "Tutor ".l:tutor
  57. else
  58. exe "help ".l:target
  59. endif
  60. endif
  61. endfunction
  62. " Folding And Info: {{{1
  63. function! tutor#TutorFolds()
  64. if getline(v:lnum) =~# '^#\{1,6}'
  65. return ">". len(matchstr(getline(v:lnum), '^#\{1,6}'))
  66. else
  67. return "="
  68. endif
  69. endfunction
  70. " Marks: {{{1
  71. function! tutor#ApplyMarks()
  72. hi! link tutorExpect Special
  73. if exists('b:tutor_metadata') && has_key(b:tutor_metadata, 'expect')
  74. let b:tutor_sign_id = 1
  75. for expct in keys(b:tutor_metadata['expect'])
  76. let lnum = eval(expct)
  77. call matchaddpos('tutorExpect', [lnum])
  78. call tutor#CheckLine(lnum)
  79. endfor
  80. endif
  81. endfunction
  82. function! tutor#ApplyMarksOnChanged()
  83. if exists('b:tutor_metadata') && has_key(b:tutor_metadata, 'expect')
  84. let lnum = line('.')
  85. if index(keys(b:tutor_metadata['expect']), string(lnum)) > -1
  86. call tutor#CheckLine(lnum)
  87. endif
  88. endif
  89. endfunction
  90. function! tutor#CheckLine(line)
  91. if exists('b:tutor_metadata') && has_key(b:tutor_metadata, 'expect')
  92. let bufn = bufnr('%')
  93. let ctext = getline(a:line)
  94. let signs = sign_getplaced(bufn, {'lnum': a:line})[0].signs
  95. if !empty(signs)
  96. call sign_unplace('', {'id': signs[0].id})
  97. endif
  98. if b:tutor_metadata['expect'][string(a:line)] == -1 || ctext ==# b:tutor_metadata['expect'][string(a:line)]
  99. exe "sign place ".b:tutor_sign_id." line=".a:line." name=tutorok buffer=".bufn
  100. else
  101. exe "sign place ".b:tutor_sign_id." line=".a:line." name=tutorbad buffer=".bufn
  102. endif
  103. let b:tutor_sign_id+=1
  104. endif
  105. endfunction
  106. " Tutor Cmd: {{{1
  107. function! s:Locale()
  108. if exists('v:lang') && v:lang =~ '\a\a'
  109. let l:lang = v:lang
  110. elseif $LC_ALL =~ '\a\a'
  111. let l:lang = $LC_ALL
  112. elseif $LC_MESSAGES =~ '\a\a' || $LC_MESSAGES ==# "C"
  113. " LC_MESSAGES=C can be used to explicitly ask for English messages while
  114. " keeping LANG non-English; don't set l:lang then.
  115. if $LC_MESSAGES =~ '\a\a'
  116. let l:lang = $LC_MESSAGES
  117. endif
  118. elseif $LANG =~ '\a\a'
  119. let l:lang = $LANG
  120. else
  121. let l:lang = 'en_US'
  122. endif
  123. return split(l:lang, '_')
  124. endfunction
  125. function! s:GlobPath(lp, pat)
  126. if version >= 704 && has('patch279')
  127. return globpath(a:lp, a:pat, 1, 1)
  128. else
  129. return split(globpath(a:lp, a:pat, 1), '\n')
  130. endif
  131. endfunction
  132. function! s:Sort(a, b)
  133. let mod_a = fnamemodify(a:a, ':t')
  134. let mod_b = fnamemodify(a:b, ':t')
  135. if mod_a == mod_b
  136. let retval = 0
  137. elseif mod_a > mod_b
  138. if match(mod_a, '^vim-') > -1 && match(mod_b, '^vim-') == -1
  139. let retval = -1
  140. else
  141. let retval = 1
  142. endif
  143. else
  144. if match(mod_b, '^vim-') > -1 && match(mod_a, '^vim-') == -1
  145. let retval = 1
  146. else
  147. let retval = -1
  148. endif
  149. endif
  150. return retval
  151. endfunction
  152. function! s:GlobTutorials(name)
  153. " search for tutorials:
  154. " 1. non-localized
  155. let l:tutors = s:GlobPath(&rtp, 'tutor/'.a:name.'.tutor')
  156. " 2. localized for current locale
  157. let l:locale_tutors = s:GlobPath(&rtp, 'tutor/'.s:Locale()[0].'/'.a:name.'.tutor')
  158. " 3. fallback to 'en'
  159. if len(l:locale_tutors) == 0
  160. let l:locale_tutors = s:GlobPath(&rtp, 'tutor/en/'.a:name.'.tutor')
  161. endif
  162. call extend(l:tutors, l:locale_tutors)
  163. return uniq(sort(l:tutors, 's:Sort'), 's:Sort')
  164. endfunction
  165. function! tutor#TutorCmd(tutor_name)
  166. if match(a:tutor_name, '[[:space:]]') > 0
  167. echom "Only one argument accepted (check spaces)"
  168. return
  169. endif
  170. if a:tutor_name == ''
  171. let l:tutor_name = 'vim-01-beginner.tutor'
  172. else
  173. let l:tutor_name = a:tutor_name
  174. endif
  175. if match(l:tutor_name, '\.tutor$') > 0
  176. let l:tutor_name = fnamemodify(l:tutor_name, ':r')
  177. endif
  178. let l:tutors = s:GlobTutorials(l:tutor_name)
  179. if len(l:tutors) == 0
  180. echom "No tutorial with that name found"
  181. return
  182. endif
  183. if len(l:tutors) == 1
  184. let l:to_open = l:tutors[0]
  185. else
  186. let l:idx = 0
  187. let l:candidates = ['Several tutorials with that name found. Select one:']
  188. for candidate in map(copy(l:tutors),
  189. \'fnamemodify(v:val, ":h:h:t")."/".s:Locale()[0]."/".fnamemodify(v:val, ":t")')
  190. let l:idx += 1
  191. call add(l:candidates, l:idx.'. '.candidate)
  192. endfor
  193. let l:tutor_to_open = inputlist(l:candidates)
  194. let l:to_open = l:tutors[l:tutor_to_open-1]
  195. endif
  196. call tutor#SetupVim()
  197. exe "edit ".l:to_open
  198. call tutor#ApplyTransform()
  199. endfunction
  200. function! tutor#TutorCmdComplete(lead,line,pos)
  201. let l:tutors = s:GlobTutorials('*')
  202. let l:names = uniq(sort(map(l:tutors, 'fnamemodify(v:val, ":t:r")'), 's:Sort'))
  203. return join(l:names, "\n")
  204. endfunction
  205. function! tutor#ApplyTransform()
  206. if has('win32')
  207. sil! %s/{unix:(\(.\{-}\)),win:(\(.\{-}\))}/\2/g
  208. else
  209. sil! %s/{unix:(\(.\{-}\)),win:(\(.\{-}\))}/\1/g
  210. endif
  211. normal! gg0
  212. endfunction