neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

tutor.vim (6912B)


      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 && foldlevel(line('.')) > 0
     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 " Tutor Cmd: {{{1
     81 
     82 function! s:Locale()
     83    " Make sure l:lang exists before returning.
     84    let l:lang = 'en_US'
     85    if exists('v:lang') && v:lang =~ '\a\a'
     86        let l:lang = v:lang
     87    elseif $LC_ALL =~ '\a\a'
     88        let l:lang = $LC_ALL
     89    elseif $LC_MESSAGES =~ '\a\a' || $LC_MESSAGES ==# "C"
     90      " LC_MESSAGES=C can be used to explicitly ask for English messages while
     91      " keeping LANG non-English; don't set l:lang then.
     92      if $LC_MESSAGES =~ '\a\a'
     93        let l:lang = $LC_MESSAGES
     94      endif
     95    elseif $LANG =~ '\a\a'
     96        let l:lang = $LANG
     97    endif
     98    return split(l:lang, '_')
     99 endfunction
    100 
    101 function! s:GlobPath(lp, pat)
    102    if version >= 704 && has('patch279')
    103        return globpath(a:lp, a:pat, 1, 1)
    104    else
    105        return split(globpath(a:lp, a:pat, 1), '\n')
    106    endif
    107 endfunction
    108 
    109 function! s:Sort(a, b)
    110    let mod_a = fnamemodify(a:a, ':t')
    111    let mod_b = fnamemodify(a:b, ':t')
    112    if mod_a == mod_b
    113        let retval =  0
    114    elseif mod_a > mod_b
    115        if match(mod_a, '^vim-') > -1 && match(mod_b, '^vim-') == -1
    116            let retval = -1
    117        else
    118            let retval = 1
    119        endif
    120    else
    121        if match(mod_b, '^vim-') > -1 && match(mod_a, '^vim-') == -1
    122            let retval = 1
    123        else
    124            let retval = -1
    125        endif
    126    endif
    127    return retval
    128 endfunction
    129 
    130 " returns a list of all tutor files matching the given name
    131 function! tutor#GlobTutorials(name, locale)
    132    let locale = a:locale
    133    " pack/*/start/* are not reported in &rtp
    134    let rtp = nvim_list_runtime_paths()
    135        \ ->map({_, v -> escape(v:lua.vim.fs.normalize(v), ',')})
    136        \ ->join(',')
    137    " search for tutorials:
    138    " 1. non-localized
    139    let l:tutors = s:GlobPath(rtp, 'tutor/'.a:name.'.tutor')
    140    " 2. localized for current locale
    141    let l:locale_tutors = s:GlobPath(rtp, 'tutor/'.locale.'/'.a:name.'.tutor')
    142    " 3. fallback to 'en'
    143    if len(l:locale_tutors) == 0
    144        let l:locale_tutors = s:GlobPath(rtp, 'tutor/en/'.a:name.'.tutor')
    145    endif
    146    call extend(l:tutors, l:locale_tutors)
    147    return uniq(sort(l:tutors, 's:Sort'), 's:Sort')
    148 endfunction
    149 
    150 function! tutor#TutorCmd(tutor_name)
    151    if match(a:tutor_name, '[[:space:]]') > 0
    152        echom "Only one argument accepted (check spaces)"
    153        return
    154    endif
    155 
    156    if a:tutor_name == ''
    157        let l:tutor_name = 'vim-01-beginner.tutor'
    158    else
    159        let l:tutor_name = a:tutor_name
    160    endif
    161 
    162    if match(l:tutor_name, '\.tutor$') > 0
    163        let l:tutor_name = fnamemodify(l:tutor_name, ':r')
    164    endif
    165 
    166    let l:tutors = tutor#GlobTutorials(l:tutor_name, s:Locale()[0])
    167 
    168    if len(l:tutors) == 0
    169        echom "No tutorial with that name found"
    170        return
    171    endif
    172 
    173    if len(l:tutors) == 1
    174        let l:to_open = l:tutors[0]
    175    else
    176        let l:idx = 0
    177        let l:candidates = ['Several tutorials with that name found. Select one:']
    178        for candidate in map(copy(l:tutors),
    179                    \'fnamemodify(v:val, ":h:h:t")."/".s:Locale()[0]."/".fnamemodify(v:val, ":t")')
    180            let l:idx += 1
    181            call add(l:candidates, l:idx.'. '.candidate)
    182        endfor
    183        let l:tutor_to_open = inputlist(l:candidates)
    184        let l:to_open = l:tutors[l:tutor_to_open-1]
    185    endif
    186 
    187    call tutor#SetupVim()
    188    exe "drop ".fnameescape(l:to_open)
    189    call tutor#EnableInteractive(v:true)
    190    call tutor#ApplyTransform()
    191 endfunction
    192 
    193 function! tutor#TutorCmdComplete(lead,line,pos)
    194    let l:tutors = tutor#GlobTutorials('*', s:Locale()[0])
    195    let l:names = uniq(sort(map(l:tutors, 'fnamemodify(v:val, ":t:r")'), 's:Sort'))
    196    return join(l:names, "\n")
    197 endfunction
    198 
    199 " Enables/disables interactive mode.
    200 function! tutor#EnableInteractive(enable)
    201    let enable = a:enable
    202    if enable
    203        setlocal buftype=nowrite
    204        setlocal concealcursor+=inv
    205        setlocal conceallevel=2
    206        lua require('nvim.tutor').apply_marks()
    207        augroup tutor_interactive
    208            autocmd! TextChanged,TextChangedI <buffer> lua require('nvim.tutor').apply_marks_on_changed()
    209        augroup END
    210    else
    211        setlocal buftype<
    212        setlocal concealcursor<
    213        setlocal conceallevel<
    214        if exists('#tutor_interactive')
    215            autocmd! tutor_interactive * <buffer>
    216        endif
    217    endif
    218 endfunction
    219 
    220 function! tutor#ApplyTransform()
    221    if has('win32')
    222        sil! %s/{unix:(\(.\{-}\)),win:(\(.\{-}\))}/\2/g
    223    else
    224        sil! %s/{unix:(\(.\{-}\)),win:(\(.\{-}\))}/\1/g
    225    endif
    226    normal! gg0
    227 endfunction