preservim / vim-markdown

Markdown Vim Mode
4.67k stars 521 forks source link

Include text object for fenced code block? #282

Open schoettl opened 8 years ago

schoettl commented 8 years ago

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

Gullumluvl commented 6 years ago

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!

lfilho commented 5 years ago

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

pyrho commented 4 years ago

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>
schoettl commented 4 years ago

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.

pyrho commented 3 years ago

@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>)

jdhao commented 3 years ago

@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
schoettl commented 3 years ago

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?

jdhao commented 3 years ago

@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`.

emilBeBri commented 2 months ago

to anyone seeing this, I made a version that seems to work better - I am not the best vimscripter to be sure, so it may be crude. but it seems reliable so far:


function! SelectCodeBlock()
    let l:line_num = line('.')
    let l:lines = getline(1, '$')
    let l:start = -1
    let l:end = -1

    " Find the start of the code block
    for l:i in range(l:line_num - 1, 0, -1)
        if l:lines[l:i] =~ '^```'
            let l:start = l:i + 1
            break
        endif
    endfor

    " Find the end of the code block
    for l:i in range(l:line_num, len(l:lines))
        if l:lines[l:i] =~ '^```'
            let l:end = l:i + 1
            break
        endif
    endfor

    " If both start and end are found and cursor is within the block
    if l:start != -1 && l:end != -1 && l:start <= l:line_num && l:line_num <= l:end
        execute 'normal! ' . l:start . 'GV' . l:end . 'G'
    endif
endfunction

function! SelectCodeBlockInner()
    call SelectCodeBlock()
    normal! kojo
endfunction

" Define text objects for code blocks
xnoremap ic :<C-u>call SelectCodeBlockInner()<CR>
onoremap ic :<C-u>call SelectCodeBlockInner()<CR>
xnoremap ac :<C-u>call SelectCodeBlock()<CR>
onoremap ac :<C-u>call SelectCodeBlock()<CR>

" Normal mode mappings
nnoremap vac :call SelectCodeBlock()<CR>
nnoremap vic :call SelectCodeBlockInner()<CR>