patch 9.1.0836: The vimtutor can be improved Commit: https://github.com/vim/vim/commit/a54816b884157f6b7973a188f85c708d15cbf72f Author: Yegappan Lakshmanan <yegap...@yahoo.com> Date: Sun Nov 3 10:49:23 2024 +0100
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 <c...@256bit.org> Signed-off-by: Yegappan Lakshmanan <yegap...@yahoo.com> diff --git a/Filelist b/Filelist index 7d2074a04..4fbcc0a2f 100644 --- a/Filelist +++ b/Filelist @@ -762,6 +762,10 @@ RT_ALL = \ runtime/tools/[a-z]*[a-z0-9] \ runtime/tutor/README.txt \ runtime/tutor/tutor \ + runtime/tutor/en/vim-01-beginner.tutor \ + runtime/tutor/en/vim-01-beginner.tutor.json \ + runtime/tutor/tutor.tutor \ + runtime/tutor/tutor.tutor.json \ runtime/tutor/tutor.vim \ runtime/vimrc_example.vim \ runtime/pack/dist/opt/cfilter/plugin/cfilter.vim \ diff --git a/runtime/autoload/tutor.vim b/runtime/autoload/tutor.vim new file mode 100644 index 000000000..3265fdde3 --- /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'), " ")) +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 =~ ' ' + let l:lang = v:lang + elseif $LC_ALL =~ ' ' + let l:lang = $LC_ALL + elseif $LANG =~ ' ' + 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), ' ') + 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, " ") +endfunction diff --git a/runtime/defaults.vim b/runtime/defaults.vim index 459841ffc..4e58233ea 100644 --- a/runtime/defaults.vim +++ b/runtime/defaults.vim @@ -1,7 +1,7 @@ " The default vimrc file. " " Maintainer: The Vim Project <https://github.com/vim/vim> -" Last change: 2023 Aug 10 +" Last Change: 2024 Nov 03 " Former Maintainer: Bram Moolenaar <b...@vim.org> " " This is loaded if no vimrc file was found. @@ -107,11 +107,11 @@ if 1 " (happens when dropping a file on gvim), for a commit or rebase message " (likely a different one than last time), and when using xxd(1) to filter " and edit binary files (it transforms input files back and forth, causing - " them to have dual nature, so to speak) + " them to have dual nature, so to speak) or when running the new tutor autocmd BufReadPost * \ let line = line("'\"") \ | if line >= 1 && line <= line("$") && &filetype !~# 'commit' - \ && index(['xxd', 'gitrebase'], &filetype) == -1 + \ && index(['xxd', 'gitrebase', 'tutor'], &filetype) == -1 \ | execute "normal! g`\"" \ | endif diff --git a/runtime/doc/Make_all.mak b/runtime/doc/Make_all.mak index f36ad7eca..ccc429b0c 100644 --- a/runtime/doc/Make_all.mak +++ b/runtime/doc/Make_all.mak @@ -75,6 +75,7 @@ DOCS = \ pi_paren.txt \ pi_spec.txt \ pi_tar.txt \ + pi_tutor.txt \ pi_vimball.txt \ pi_zip.txt \ popup.txt \ @@ -228,6 +229,7 @@ HTMLS = \ pi_paren.html \ pi_spec.html \ pi_tar.html \ + pi_tutor.html \ pi_vimball.html \ pi_zip.html \ popup.html \ diff --git a/runtime/doc/pi_tutor.txt b/runtime/doc/pi_tutor.txt new file mode 100644 index 000000000..7aaafd19a --- /dev/null +++ b/runtime/doc/pi_tutor.txt @@ -0,0 +1,51 @@ +*pi_tutor.txt* For Vim version 9.1. Last change: 2024 Nov 02 + +INTERACTIVE TUTORIALS FOR VIM *vim-tutor-mode* + +vim-tutor-mode provides a system to follow and create interactive tutorials +for vim and third party plugins. It replaces the venerable `vimtutor` system. + +============================================================================== +1. Usage *vim-tutor-usage* + +vim-tutor-mode tutorials are hypertext documents, they have rich text and +contain links. To stand out from the rest of the text, links are underlined. +You can follow them by placing the cursor over them and pressing <Enter>, or +by double-clicking them. + +1.1 Commands +------------ + *:Tutor* +:Tutor {tutorial} Opens a tutorial. Command-line completion for + {tutorial} is provided, the candidates are a list of + '.tutor' files found in the 'tutor/' folder in + the 'runtimepath'. Tutorials prefixed with 'vim-' will + always be shown first. + + If no {tutorial} is provided, the command starts the + 'vim-01-beginner' tutorial, which is equivalent to + Vim's `vimtutor`. + +============================================================================= +2. Creating tutorials *vim-tutor-create* + +Writing vim-tutor-mode tutorials is easy. For an overview of the format used, +please consult the 'tutor.tutor' file: > + + :Tutor tutor +< +New tutorials must be placed in the 'tutor/' folder in the 'runtimepath' +to be detected by the :Tutor command. + +It is recommended to use a less formal style when writing tutorials than in +regular documentation (unless the content requires it). + +============================================================================ +3. Contributing + +Development of the plugin is done over at github [1]. Feel free to report +issues and make suggestions. + +[1]: https://github.com/fmoralesc/vim-tutor-mode + +" vim: set ft=help : diff --git a/runtime/doc/tags b/runtime/doc/tags index 79d9dafb9..37a8e266d 100644 --- a/runtime/doc/tags +++ b/runtime/doc/tags @@ -2177,6 +2177,7 @@ $quote eval.txt /*$quote* :Termdebug terminal.txt /*:Termdebug* :TermdebugCommand terminal.txt /*:TermdebugCommand* :Texplore pi_netrw.txt /*:Texplore* +:Tutor pi_tutor.txt /*:Tutor* :Until terminal.txt /*:Until* :Up terminal.txt /*:Up* :UseVimball pi_vimball.txt /*:UseVimball* @@ -9438,6 +9439,7 @@ pi_netrw.txt pi_netrw.txt /*pi_netrw.txt* pi_paren.txt pi_paren.txt /*pi_paren.txt* pi_spec.txt pi_spec.txt /*pi_spec.txt* pi_tar.txt pi_tar.txt /*pi_tar.txt* +pi_tutor.txt pi_tutor.txt /*pi_tutor.txt* pi_vimball.txt pi_vimball.txt /*pi_vimball.txt* pi_zip.txt pi_zip.txt /*pi_zip.txt* pkzip options.txt /*pkzip* @@ -11244,6 +11246,9 @@ vim-script-intro usr_41.txt /*vim-script-intro* vim-script-library eval.txt /*vim-script-library* vim-security intro.txt /*vim-security* vim-shebang various.txt /*vim-shebang* +vim-tutor-create pi_tutor.txt /*vim-tutor-create* +vim-tutor-mode pi_tutor.txt /*vim-tutor-mode* +vim-tutor-usage pi_tutor.txt /*vim-tutor-usage* vim-use intro.txt /*vim-use* vim-variable eval.txt /*vim-variable* vim.b if_lua.txt /*vim.b* diff --git a/runtime/doc/usr_01.txt b/runtime/doc/usr_01.txt index fdf1b5386..9902691ba 100644 --- a/runtime/doc/usr_01.txt +++ b/runtime/doc/usr_01.txt @@ -1,4 +1,4 @@ -*usr_01.txt* For Vim version 9.1. Last change: 2024 May 11 +*usr_01.txt* For Vim version 9.1. Last change: 2024 Nov 03 VIM USER MANUAL - by Bram Moolenaar @@ -107,6 +107,8 @@ For more info see |vimrc| and |compatible-default|. ============================================================================== *01.3* Using the Vim tutor *tutor* *vimtutor* +For the interactive tutor, see |vim-tutor-mode| + Instead of reading the text (boring!) you can use the vimtutor to learn your first Vim commands. This is a 30-minute tutorial that teaches the most basic Vim functionality hands-on. diff --git a/runtime/doc/version9.txt b/runtime/doc/version9.txt index 5f9250348..d016ccfb1 100644 --- a/runtime/doc/version9.txt +++ b/runtime/doc/version9.txt @@ -1,4 +1,4 @@ -*version9.txt* For Vim version 9.1. Last change: 2024 Nov 02 +*version9.txt* For Vim version 9.1. Last change: 2024 Nov 03 VIM REFERENCE MANUAL by Bram Moolenaar @@ -41603,6 +41603,8 @@ Changed~ - the putty terminal is detected using an |TermResponse| autocommand in |defaults.vim| and Vim switches to a dark background - the |help-TOC| package is included to ease navigating the documentation. +- an interactive tutor plugin has been included |vim-tutor-mode|, can be + started via |:Tutor| *added-9.2* Added ~ diff --git a/runtime/filetype.vim b/runtime/filetype.vim index 36461f69f..f2ddcc676 100644 --- a/runtime/filetype.vim +++ b/runtime/filetype.vim @@ -2589,6 +2589,9 @@ au BufNewFile,BufReadPost *.tsscl setf tsscl " TSV Files au BufNewFile,BufRead *.tsv setf tsv +" Tutor mode +au BufNewFile,BufReadPost *.tutor setf tutor + " TWIG files au BufNewFile,BufReadPost *.twig setf twig diff --git a/runtime/ftplugin/tutor.vim b/runtime/ftplugin/tutor.vim new file mode 100644 index 000000000..30783d979 --- /dev/null +++ b/runtime/ftplugin/tutor.vim @@ -0,0 +1,45 @@ +" vim: fdm=marker + +" Base: {{{1 +call tutor#SetupVim() + +" Buffer Settings: {{{1 +setlocal noreadonly +if !exists('g:tutor_debug') || g:tutor_debug == 0 + setlocal buftype=nofile + setlocal concealcursor+=inv + setlocal conceallevel=2 +else + setlocal buftype= + setlocal concealcursor& + setlocal conceallevel=0 +endif +setlocal noundofile + +setlocal keywordprg=:help +setlocal iskeyword=@,-,_ + +" The user will have to enable the folds himself, but we provide the foldexpr +" function. +setlocal foldmethod=manual +setlocal foldexpr=tutor#TutorFolds() +setlocal foldlevel=4 + +" Load metadata if it exists: {{{1 +if filereadable(expand('%').'.json') + call tutor#LoadMetadata() +endif + +" Mappings: {{{1 + +call tutor#SetNormalMappings() + +" Checks: {{{1 + +sign define tutorok text=✓ texthl=tutorOK +sign define tutorbad text=✗ texthl=tutorX + +if !exists('g:tutor_debug') || g:tutor_debug == 0 + call tutor#ApplyMarks() + autocmd! TextChanged,TextChangedI <buffer> call tutor#ApplyMarksOnChanged() +endif diff --git a/runtime/plugin/tutor.vim b/runtime/plugin/tutor.vim new file mode 100644 index 000000000..1411b1ac6 --- /dev/null +++ b/runtime/plugin/tutor.vim @@ -0,0 +1,6 @@ +if exists('g:loaded_tutor_mode_plugin') || &compatible + finish +endif +let g:loaded_tutor_mode_plugin = 1 + +command! -nargs=? -complete=custom,tutor#TutorCmdComplete Tutor call tutor#TutorCmd(<q-args>) diff --git a/runtime/syntax/tutor.vim b/runtime/syntax/tutor.vim new file mode 100644 index 000000000..83ca547fd --- /dev/null +++ b/runtime/syntax/tutor.vim @@ -0,0 +1,77 @@ +if exists("b:current_syntax") + finish +endif + +syn include @VIM syntax/vim.vim +unlet b:current_syntax +syn include @TUTORSHELL syntax/sh.vim +unlet b:current_syntax +syn include @VIMNORMAL syntax/vimnormal.vim + +syn match tutorLink /\[.\{-}\](.\{-})/ contains=tutorInlineNormal +syn match tutorLinkBands /\[\|\]\|(\|)/ contained containedin=tutorLink,tutorLinkAnchor conceal +syn match tutorLinkAnchor /(.\{-})/ contained containedin=tutorLink conceal +syn match tutorURL /\(https\?\|file\):\/\/[[:graph:]]\+\>\/\?/ +syn match tutorEmail /\<[[:graph:]]\+@[[:graph:]]\+\>/ +syn match tutorInternalAnchor /\*[[:alnum:]-]\+\*/ contained conceal containedin=tutorSection + +syn match tutorSection /^#\{1,6}\s.\+$/ fold contains=tutorInlineNormal +syn match tutorSectionBullet /#/ contained containedin=tutorSection + +syn match tutorTOC / -- -- You received this message from the "vim_dev" maillist. Do not top-post! Type your reply below the text you are replying to. For more information, visit http://www.vim.org/maillist.php --- You received this message because you are subscribed to the Google Groups "vim_dev" group. To unsubscribe from this group and stop receiving emails from it, send an email to vim_dev+unsubscr...@googlegroups.com. To view this discussion visit https://groups.google.com/d/msgid/vim_dev/E1t7XdP-00Aclw-8k%40256bit.org.