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