vim-markdown icon indicating copy to clipboard operation
vim-markdown copied to clipboard

Include text object for fenced code block?

Open schoettl opened this issue 8 years ago • 9 comments

Hi,

I have written text objects for fenced code blocks.

E.g. cif replaces the code inside, daf deletes a code block with fence markers, or vif visually selects code inside so it can be pasted (middle mouse click) to an interpreter.

Do you consider this a good feature? Would you accept an pull request?

Thanks, Jakob

schoettl avatar Jun 26 '16 10:06 schoettl

Very interesting, I concur too! Why not extend the text object to any code block (default markdown indented ones too)? Would love to use that in combination with a plugin like Slimux!

Gullumluvl avatar Apr 04 '18 16:04 Gullumluvl

I was looking for that! @schoettl can you share what you've done?

lfilho avatar Jul 29 '19 22:07 lfilho

Here is my attempt at creating text objects for fenced code blocks. It is severly untested so YMMV (:

function! s:inCodeFence()
    " Search backwards for the opening of the code fence.
	call search('^```.*$', 'bceW')
    " Move one line down
	normal! j
    " Move to the begining of the line at start selecting
	normal! 0v
    " Search forward for the closing of the code fence.
	call search("```", 'ceW')

	normal! kg_
endfunction

function! s:aroundCodeFence()
    " Search backwards for the opening of the code fence.
	call search('^```.*$', 'bcW')
	normal! v$
    " Search forward for the closing of the code fence.
	call search('```', 'eW')
endfunction

autocmd Filetype markdown xnoremap <silent> if :<c-u>call <sid>inCodeFence()<cr>
autocmd Filetype markdown onoremap <silent> if :<c-u>call <sid>inCodeFence()<cr>
autocmd Filetype markdown xnoremap <silent> af :<c-u>call <sid>aroundCodeFence()<cr>
autocmd Filetype markdown onoremap <silent> af :<c-u>call <sid>aroundCodeFence()<cr>

pyrho avatar Sep 04 '20 22:09 pyrho

Sorry for the late answer, I'll also share my solution: Put a file markdown.vim into ~/.vim/ftplugin/ with this content:

function! s:SelectFencedCodeA()
    execute "normal! $?^````*$\<CR>V/^````*$\<CR>o"
endfunction

function! s:SelectFencedCodeI()
    call <SID>SelectFencedCodeA()
    normal! joko
endfunction

nmap     <buffer>         va`      :call <SID>SelectFencedCodeA()<CR>
nmap     <buffer>         vi`      :call <SID>SelectFencedCodeI()<CR>
onoremap <buffer><silent> a`       :call <SID>SelectFencedCodeA()<CR>
onoremap <buffer><silent> i`       :call <SID>SelectFencedCodeI()<CR>

" Good default for Markdown:
set textwidth=80

@pyrho I'm not sure but I think, your solution registers the key mapping in every buffer. That's why I use <buffer>.

PS: My current solution only works for "plain" fenced code without a language. You will have to change the first backward search regex (after ?) to also match a language name.

schoettl avatar Sep 05 '20 13:09 schoettl

@schoettl Thanks for sharing. Indeed Creating the mappings via ftplugin/markdown.vim is better than my autocmd on FileType (after/ftplugin/ maybe even better suited for user-defined mappings).

your solution registers the key mapping in every buffer.

I think you are right, <buffer> is better (vimways confirms this ^^ I didn't know about <buffer>)

pyrho avatar Sep 10 '20 21:09 pyrho

@pyrho @schoettl IIRIC, markdown code blocks can have indented spaces before them. So the code block does not necessarily need to begin with backticks. It may well begin with white spaces. You may add \s* before your pattern.

@schoettl I think we should use three backticks instead of four. Also setting code blocks text objects to i` and a` will shadow the text objects for inline markdown code. Maybe it should be better avoided.

This is my attempt to provide a text object for code blocks:

vnoremap <silent> ic :<C-U>call <SID>MdCodeBlockTextObj('i')<CR>
onoremap <silent> ic :<C-U>call <SID>MdCodeBlockTextObj('i')<CR>

vnoremap <silent> ac :<C-U>call <SID>MdCodeBlockTextObj('a')<CR>
onoremap <silent> ac :<C-U>call <SID>MdCodeBlockTextObj('a')<CR>

function! s:MdCodeBlockTextObj(type) abort
  " the parameter type specify whether it is inner text objects or arround
  " text objects.
  let start_row = searchpos('\s*```', 'bn')[0]
  let end_row = searchpos('\s*```', 'n')[0]

  let buf_num = bufnr()
  if a:type ==# 'i'
    let start_row += 1
    let end_row -= 1
  endif
  " echo a:type start_row end_row

  call setpos("'<", [buf_num, start_row, 1, 0])
  call setpos("'>", [buf_num, end_row, 1, 0])
  execute 'normal! `<V`>'
endfunction

jdhao avatar Nov 12 '20 08:11 jdhao

Hi @jdhao

@schoettl I think we should use three backticks instead of four.

My code allows three or more backticks. Actually, it's even more special: the fence must not be indented more than three spaces ^^

Also setting code blocks text objects to i` and a` will shadow the text objects for inline markdown code. Maybe it should be better avoided.

Do you know which plugin is it, that already provides the inline code text objects? Or is it built-in in markdown.vim?

schoettl avatar Nov 12 '20 23:11 schoettl

@schoettl The i` and a` objects are builtin at least in Neovim, as can be verified by using nvim -u NORC test.md and use these two objects. There is also help tag for them: see :h i` and :h a` .

jdhao avatar Nov 13 '20 02:11 jdhao