Repository: gpanders/vim-medieval
Branch: master
Commit: 3bcb574c2050
Files: 9
Total size: 33.4 KB
Directory structure:
gitextract_t68na24_/
├── .github/
│ └── workflows/
│ └── test.yml
├── LICENSE
├── README.md
├── autoload/
│ └── medieval.vim
├── doc/
│ └── medieval.txt
├── plugin/
│ └── medieval.vim
├── run-test.sh
└── test/
├── medieval.vader
└── vimrc
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/test.yml
================================================
name: Test
on:
push:
branches: [master]
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Check Vim
run: which vim && vim --version | head -1
- name: Run tests
run: ./run-test.sh
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2020 Greg Anders
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# vim-medieval
Evaluate Markdown code blocks within Vim.
[](https://asciinema.org/a/306995)
## Description
Medieval allows you to evaluate code blocks in Markdown buffers of the
following form:
````markdown
```bash
echo "Hello world!"
```
````
By placing your cursor anywhere in the code block above and running
`:EvalBlock`, Medieval will print the result of evaluating the block (in this
case, it will echo "Hello world!")
You can send the output of evaluation into another code block, allowing
you to do a primitive style of literate programming. You can accomplish this
by adding a "target" parameter to your code block and creating a second code
block with a "name" parameter. The output of the evaluation of your code block
will be redirected to the targeted block. For example:
````markdown
<!-- target: squares -->
```python
print([x*x for x in range(5)])
```
<!-- name: squares -->
```
```
````
If you run `:EvalBlock` in the first code block, the second block will become
````markdown
<!-- name: squares -->
```
[0, 1, 4, 9, 16]
```
````
Medieval can do a lot more. Read `:h medieval` for the full documentation.
## Create a mapping
Medieval does not create any mappings by default, but you can easily create one
yourself by adding the following to the file
`~/.vim/after/ftplugin/markdown.vim` (create it if it does not yet exist):
```vim
nmap <buffer> Z! <Plug>(medieval-eval)
```
## Limitations
For now, Medieval only works in Markdown buffers. If you'd like to see support
added for other file types, please see the [Contributing](#contributing)
section.
## Contributing
Please feel free to contribute changes or bug fixes. You can [send patches][]
to <git@gpanders.com> or submit a pull request on [GitHub][].
[send patches]: https://git-send-email.io/
[Github]: https://github.com/gpanders/vim-medieval
================================================
FILE: autoload/medieval.vim
================================================
const s:fences = [#{start: '\([`~]\{3,}\)\s*\%({\s*\.\?\)\?\(\a\+\)\?', end: '\1', lang: 2,}, #{start: '\$\$'}]
let s:opts = ['name', 'target', 'require', 'tangle']
let s:optspat = '\(' . join(s:opts, '\|') . '\):\s*\([0-9A-Za-z_+.$#&/-]\+\)'
let s:optionfmt = '<!-- %s -->'
let s:optionpat = '^\s*<!--\s*'
function! s:error(msg) abort
if empty(a:msg)
return
endif
echohl ErrorMsg
echom 'medieval: ' . a:msg
echohl None
endfunction
" Check the v:register variable for a valid value to see if the user wants to
" copy output to a register
function! s:validreg(reg) abort
if a:reg ==# ''
return v:false
endif
if a:reg ==# '"'
return v:false
endif
if &clipboard =~# '^unnamed' && (a:reg ==# '*' || a:reg ==# '+')
return v:false
endif
return v:true
endfunction
" Generate search pattern to match the start of any valid fence
function! s:fencepat(fences) abort
return join(map(copy(a:fences), 'v:val.start'), '\|')
endfunction
" Find a code block with the given name and return the start and end lines.
" For example, s:findblock('foo') will find the following block:
"
" <!-- name: foo -->
" ```
" ```
function! s:findblock(ft, name) abort
let fences = s:fences + get(g:, 'medieval_fences', [])
let fencepat = s:fencepat(fences)
let curpos = getcurpos()[1:]
call cursor(1, 1)
let pat = get(get(g:, 'medieval_option_pat', {}), a:ft, s:optionpat)
while 1
let start = search(pat . s:optspat, 'cW')
if !start || start == line('$')
call cursor(curpos)
return [0, 0]
endif
" Move the cursor so that we don't match on the current line again
call cursor(start + 1, 1)
if getline(start) =~# '\<name:\s*' . a:name
if getline('.') =~# '^\s*\%(' . fencepat . '\)'
break
endif
endif
endwhile
let endpat = ''
for fence in fences
let matches = matchlist(getline('.'), fence.start)
if !empty(matches)
" If 'end' pattern is not defined, copy the opening
" delimiter
let endpat = get(fence, 'end', fence.start)
" Replace any instances of \0, \1, \2, ... with the
" submatch from the opening delimiter
let endpat = substitute(endpat, '\\\(\d\)', '\=matches[submatch(1)]', 'g')
break
endif
endfor
let end = search('^\s*' . endpat . '\s*$', 'nW')
call cursor(curpos)
return [start, end]
endfunction
function! s:createblock(ft, start, name, fence) abort
let opt = printf('name: %s', a:name)
let marker = printf(get(get(g:, 'medieval_option_fmt', {}), a:ft, s:optionfmt), opt)
call append(a:start, ['', marker, a:fence.start, a:fence.end])
endfunction
function! s:extend(list, val)
let data = a:val
if data[-1] == ''
let data = data[:-2]
end
return extend(a:list, data)
endfunction
" Wrapper around job start functions for both neovim and vim
function! s:jobstart(cmd, cb) abort
let output = []
if !get(g:, 'medieval_sync') && exists('*jobstart')
call jobstart(a:cmd, {
\ 'on_stdout': {_, data, ... -> s:extend(output, data)},
\ 'on_stderr': {_, data, ... -> s:extend(output, data)},
\ 'on_exit': {... -> a:cb(output)},
\ 'stdout_buffered': 1,
\ 'stderr_buffered': 1,
\ })
elseif !get(g:, 'medieval_sync') && exists('*job_start')
call job_start(a:cmd, {
\ 'callback': {_, data -> add(output, data)},
\ 'exit_cb': {... -> a:cb(output)},
\ })
elseif exists('*systemlist')
let output = systemlist(join(a:cmd))
call a:cb(output)
else
call s:error('Unable to start job')
endif
endfunction
" Parse an options string on the given line number
function! s:parseopts(ft, lnum) abort
let opts = {}
let line = getline(a:lnum)
let pat = get(get(g:, 'medieval_option_pat', {}), a:ft, s:optionpat)
if line =~# pat . s:optspat
let cnt = 0
while 1
let matches = matchlist(line, s:optspat, 0, cnt)
if empty(matches)
break
endif
let opts[matches[1]] = matches[2]
let cnt += 1
endwhile
endif
return opts
endfunction
function! s:require(ft, name) abort
let [start, end] = s:findblock(a:ft, a:name)
if !end
return []
endif
let block = getline(start + 2, end - 1)
let opts = s:parseopts(a:ft, start)
if has_key(opts, 'require')
return s:require(a:ft, opts.require) + block
endif
return block
endfunction
function! s:callback(context, output) abort
let opts = a:context.opts
if !has_key(opts, 'tangle')
call delete(a:context.fname)
endif
if empty(a:output)
return
endif
if has_key(opts, 'complete')
call opts.complete(a:context, a:output)
endif
let start = a:context.start
let end = a:context.end
if get(opts, 'target', '') !=# ''
if opts.target ==# 'self'
call deletebufline('%', start + 1, end - 1)
call append(start, a:output)
elseif opts.target =~# '^@'
call setreg(opts.target[1], a:output)
elseif expand(opts.target) =~# '/'
let f = fnamemodify(expand(opts.target), ':p')
call writefile(a:output, f)
echo 'Output written to ' . f
else
let [tstart, tend] = s:findblock(a:context.filetype, opts.target)
if !tstart
call s:createblock(a:context.filetype, end, opts.target, #{start: getline(start), end: getline(end)})
let tstart = end + 2
let tend = tstart + 1
endif
if !tend
return s:error('Block "' . opts.target . '" doesn''t have a closing fence')
endif
call deletebufline('%', tstart + 2, tend - 1)
call append(tstart + 1, a:output)
endif
else
" Open result in scratch buffer
if &splitbelow
botright new
else
topleft new
endif
call append(0, a:output)
call deletebufline('%', '$')
exec 'resize' &previewheight
setlocal buftype=nofile bufhidden=delete nobuflisted noswapfile winfixheight
wincmd p
endif
if has_key(opts, 'after')
call opts.after(a:context, a:output)
endif
endfunction
function! medieval#evalrange(line1, line2, target) abort
if !exists('g:medieval_langs')
call s:error('g:medieval_langs is unset')
return
endif
let fences = filter((s:fences + get(g:, 'medieval_fences', [])), 'has_key(v:val, "lang")')
let view = winsaveview()
" Collect opening fence lines with a language within the range
let blocks = []
let lnum = a:line1
while lnum <= a:line2
let line = getline(lnum)
for fence in fences
let matches = matchlist(line, fence.start)
if !empty(matches) && matches[fence.lang] !=# ''
" Skip named blocks without a target — these are output
" destinations or dependency blocks, not source blocks
let opts = s:parseopts(&filetype, lnum - 1)
if !has_key(opts, 'name') || has_key(opts, 'target')
call add(blocks, lnum)
endif
break
endif
endfor
let lnum += 1
endwhile
" Evaluate each block
for blnum in blocks
call cursor(blnum, 1)
call medieval#eval(a:target)
endfor
call winrestview(view)
endfunction
function! medieval#eval(...) abort
if !exists('g:medieval_langs')
call s:error('g:medieval_langs is unset')
return
endif
let view = winsaveview()
let line = line('.')
let fences = filter((s:fences + get(g:, 'medieval_fences', [])), 'has_key(v:val, "lang")')
let fencepat = s:fencepat(fences)
let start = search(fencepat, 'bcnW')
if !start
return
endif
" If cursor is in a named destination block, find and evaluate its source
let opts = s:parseopts(&filetype, start - 1)
if has_key(opts, 'name') && !has_key(opts, 'target')
call cursor(1, 1)
let pat = get(get(g:, 'medieval_option_pat', {}), &filetype, s:optionpat)
while 1
let srcline = search(pat . s:optspat, 'cW')
if !srcline
call winrestview(view)
return s:error('No source block targeting "' . opts.name . '"')
endif
call cursor(srcline + 1, 1)
if getline(srcline) =~# '\<target:\s*' . opts.name
break
endif
endwhile
call call('medieval#eval', a:000)
call winrestview(view)
return
endif
call cursor(start, 1)
let lang = ''
let endpat = ''
for fence in fences
let matches = matchlist(getline(start), fence.start)
if !empty(matches)
let lang = matches[fence.lang]
let endpat = get(fence, 'end', fence.start)
let endpat = substitute(endpat, '\\\(\d\)', '\=matches[submatch(1)]', 'g')
break
endif
endfor
if empty(lang)
call winrestview(view)
return s:error('Could not determine language for block')
endif
if empty(endpat)
call winrestview(view)
return s:error('No end pattern')
endif
let end = search('^\s*' . endpat . '\s*$', 'nW')
if end < line
call winrestview(view)
return s:error('Closing fence not found')
endif
let langidx = index(map(copy(g:medieval_langs), 'split(v:val, "=", 1)[0]'), lang)
if langidx < 0
call winrestview(view)
echo '''' . lang . ''' not found in g:medieval_langs'
return
endif
let opts = s:parseopts(&filetype, start - 1)
if a:0 && a:1 !=# ''
let opts.target = a:1
elseif s:validreg(v:register)
let opts.target = '@' . v:register
endif
if g:medieval_langs[langidx] =~# '='
let lang = split(g:medieval_langs[langidx], '=')[-1]
endif
if !executable(lang)
call winrestview(view)
return s:error('Command not found: ' . lang)
endif
if has_key(opts, 'tangle')
let fname = expand(opts.tangle)
echo 'Tangled source code written to ' . fname
else
let fname = tempname()
if lang == "cmd"
let fname .= ".bat"
endif
endif
if a:0 > 1
call extend(opts, a:2, 'error')
endif
let context = {'opts': opts, 'start': start, 'end': end, 'fname': fname, 'filetype': &filetype}
let block = getline(start + 1, end - 1)
if has_key(opts, 'require')
let block = s:require(&filetype, opts.require) + block
endif
if has_key(opts, 'setup')
call opts.setup(context, block)
endif
call writefile(block, fname)
if lang == "cmd"
call s:jobstart([fname], function('s:callback', [context]))
else
call s:jobstart([lang, fname], function('s:callback', [context]))
endif
call winrestview(view)
endfunction
================================================
FILE: doc/medieval.txt
================================================
*medieval.txt* Evaluate Markdown code blocks in Vim
Author: Gregory Anders <greg@gpanders.com>
Repo: https://github.com/gpanders/vim-medieval
License: Same terms as Vim itself (see |license|)
*:EvalBlock* *medieval*
Medieval allows you to evaluate code blocks in Markdown buffers of the
following form:
>
```bash
echo "Hello world!"
```
<
By placing your cursor anywhere in the code block above and running
|:EvalBlock|, Medieval will open the result of evaluating the block in the
|preview-window| (in this case, it will contain the text "Hello world!")
You can also redirect the output of the evaluation into a register using
|:EvalBlock| @{0-9a-z".=*+}.
You can also use a range to evaluate multiple blocks at once. For example: >
:%EvalBlock
<
evaluates all code blocks in the entire buffer. Similarly: >
:10,50EvalBlock
<
evaluates all code blocks between lines 10 and 50. A visual selection also
works: select lines, then run |:EvalBlock|. Each block uses its own options
(target, require, etc.).
*medieval-target*
You can also send the output of evaluation into another code block, allowing
you to do a primitive style of literate programming. You can accomplish this
by adding a "target" parameter to your code block and creating a second code
block with a "name" parameter. The output of the evaluation of your code block
will be redirected to the targeted block. For example:
>
<!-- target: squares -->
```python
print([x*x for x in range(5)])
```
<!-- name: squares -->
```
```
<
If you run |:EvalBlock| in the first code block, the second block will become
>
<!-- name: squares -->
```
[0, 1, 4, 9, 16]
```
<
The target of a block can also be a file. If the target name contains a "/"
character, it is assumed to be a file path. File paths can contain environment
variables and tilde expansion. Example:
>
<!-- target: $HOME/squares.txt -->
```python
print([x*x for x in range(5)])
```
<
Note that the following will write to a code block named "squares.txt" (and
create it if it doesn't exist) instead of writing to a file called
"squares.txt":
>
<!-- target: squares.txt -->
To write to a file called "squares.txt", use: >
<!-- target: ./squares.txt -->
<
You can manually specify a target block using |:EvalBlock| {target}. With
[!], |:EvalBlock| will cause the evaluated code block to replace its own
contents with the result of its evaluation:
>
```sh
fortune
```
<
After |:EvalBlock!|:
>
```sh
The difference between art and science is that science is what we
understand well enough to explain to a computer. Art is everything else.
-- Donald Knuth, "Discover"
```
<
The language of the block being executed is detected through the text next to
the opening code fence (known as the "info string"). There is no formal
specification for how the info string should be formatted; however, Medieval
can detect info strings in any of the following formats:
>
```lang
```
```{.lang}
```
```{lang}
```
<
Whitespace is allowed before the info string. The closing "}" is not required
for the latter two styles, meaning you can use info strings such as
>
``` {.python .numberLines #my-id}
```
<
Note, however, that when using this kind of info string the language name must
be first for Medieval to correctly detect it.
The target block can be either another code block (delimited by "```" or
"~~~") or a LaTeX math block (delimited by "$$"):
>
<!-- target: math -->
```python
print(r"\text{Hello LaTeX!}")
```
<!-- name: math -->
$$
$$
<
*medieval-labels*
By default, the block labels must be of the form "<!-- {option}: {value}[,]
[{option}: {value}[,] [...]]" where {option} is one of "name", "target",
"require", or "tangle". The label can be preceeded by whitespace, but no other
characters. The option values can be composed of the following characters:
"0-9A-Za-z_+.$#&/-". Note that the closing tag of the HTML comment is not
required. This allows you to embed the code block within an HTML block comment
so that the block will not be rendered in the final output. For example:
>
<!-- target: example
```sh
echo '$ ls -1'
ls -1
```
-->
<!-- name: example -->
```sh
$ ls -1
LICENSE
README.md
after
autoload
doc
```
<
In this example, only the second block will be rendered, since the first block
is nested within an HTML comment.
The label pattern can be changed on a per-filetype basis, if needed. See
|g:medieval_option_pat|.
*medieval-require*
Code blocks can be combined using the "require" option. The argument to the
"require" option is the name of another code block which will be evaluated
before the contents of the block itself. Required blocks must use the same
language as the requiring block.
For example,
>
<!-- name: numpy -->
```python
import numpy as np
```
<!-- target: output, require: numpy -->
```python
print(np.arange(1, 5))
```
<!-- name: output -->
```
```
<
Running |:EvalBlock| in the second code block produces:
>
<!-- name: output -->
```
[1 2 3 4]
```
<
Blocks can have recursive dependencies:
>
<!-- name: first_name -->
```sh
first_name="Gregory"
```
<!-- name: full_name, require: first_name -->
```sh
full_name="$first_name Anders"
```
<!-- target: greeting, require: full_name -->
```sh
echo "Hi, my name is $full_name"
```
<
After running :EvalBlock in the block above...
>
<!-- name: greeting -->
```
Hi, my name is Gregory Anders
```
<
*medieval-tangle*
The source code in a code block can be written to a given file before
executing by using the "tangle" option. This can be used in conjunction with
the "require" keyword to combine multiple blocks together into a single
combined source file.
Example:
>
<!-- name: numpy -->
```python
import numpy as np
```
<!-- require: numpy tangle: script.py -->
```python
x = np.arange(5)
print(x)
```
<
When you run |:EvalBlock| on the second code block above, a new file called
"script.py" will be generated in your current working directory with the
contents
>
import numpy as np
x = np.arange(5)
print(x)
<
Note that the value of the "tangle" option is always interpreted as the name
of a file, regardless of whether or not it contains a / character.
If you only wish to use the tangling feature without printing the output
of the code block, you can use `/dev/null` as the block target:
>
<!-- target: /dev/null tangle: script.py -->
<
*medieval#eval()*
medieval#eval({target}[, {opts})
Evaluate the block under the cursor. To replace the contents of
the block (like |:EvalBlock!|) use "self" for {target}. If
{target} is |v:null| or an empty string then it uses the
"target" field from the block header |medieval-target| if it
exists; otherwise, output is written to the |preview-window|.
{opts} is an optional |Dict| accepting the following keys:
setup: (function) A function to be called just before
evaluating the code block. The function
accepts two arguments: a "context" |Dict|
containing the parameters used to evaluate the
block (such as the start and end line number
of the block) and the text withing the block
as a list of lines. Modifications to the block
text will affect what is evaluated.
complete: (function) A function to be called when
evaluation completes, before the output is
written to the target block. The function
accepts two arguments: a "context" |Dict|
containing the parameters used to evaluate the
block and the result of the block evaluation
as a list of lines. Modifications to the
output list will affect what is written to the
target block.
after: (function) A function to be called when
evaluation completes, but after the output is
written to the target block. The function
accepts two arguments: a "context" |Dict|
containing the parameters used to evaluate the
block and the result of the block evaluation
as a list of lines.
Example: >
function! s:complete(ctx, output)
let elapsed = reltimestr(reltime(a:ctx.start_time))
call add(a:output, 'Evaluation finished in ' . elapsed . 's')
endfunction
function! s:setup(ctx, input)
let a:ctx.start_time = reltime()
endfunction
function! s:after(ctx, input)
echo "Target has been updated."
endfunction
call medieval#eval('',
\ #{setup: function('s:setup'),
\ complete: function('s:complete'),
\ after: function('s:after')})
<
*medieval#evalrange()*
medieval#evalrange({line1}, {line2}, {target})
Evaluate all code blocks between {line1} and {line2}.
{target} is passed to |medieval#eval()| for each block. Use
an empty string to let each block use its own target. Use
"self" to replace each block's contents with its output.
*g:medieval_langs*
Medieval will only attempt to execute code blocks in languages explicitly
listed in the variable |g:medieval_langs|. The structure of this variable is a
list of strings corresponding to whitelisted languages that can be
interpreted. If a language's interpreter has a different name than the
language itself, you can use the form "{lang}={interpreter}" to specify what
interpreter should be used.
For example, to allow Medieval to run Python, Ruby, and shell scripts, use
>
let g:medieval_langs = ['python=python3', 'ruby', 'sh', 'console=bash']
<
By default, |g:medieval_langs| is empty, so you must specify this variable
yourself.
*g:medieval_fences*
You can define custom code fence delimiters using the variable
|g:medieval_fences|. This variable is a |List| of |Dict|s containing a "start"
key that defines a pattern for the opening delimiter of the code block and an
optional "end" key that defines a pattern for the closing delimiter of the
code block. If "end" is omitted, then the closing delimiter is assumed to be
the same as the opening delimiter.
For example, to use a block of the following form:
>
<!-- name: katex -->
{{< katex >}}
{{< /katex >}}
<
You can set |g:medieval_fences| to
>
let g:medieval_fences = [{
\ 'start': '{{<\s\+\(\S\+\)\s\+>}}',
\ 'end': '{{<\s\+/\1\s\+>}}',
\ }]
<
Note the use of a capture group in the "start" pattern and the use of "\1" in
the end pattern. The "\1" in the end pattern will be replaced by whatever
matches the capture group in the "start" pattern ("katex" in our example
above).
*g:medieval_option_pat*
Medieval finds labeled blocks using an "option pattern". The default is
"^\s*<!--\s*" which matches HTML comments as described in this document. This
pattern can be overriden on a per-filetype basis by adding entries to the
|g:medieval_option_pat| variable. Example: >
let g:medieval_option_pat = {}
let g:medieval_option_pat.vimwiki = '^%%\s*'
<
*g:medieval_option_fmt*
When Medieval creates a new block it will insert an option label
automatically. By default, the label will be "<!-- name: {name} -->", but this
can be overridden on a per-filetype basis by setting the
|g:medieval_option_fmt| variable. This variable is a |Dict| mapping filetype
to a |printf()| style pattern. Example: >
let g:medieval_option_fmt = {}
let g:medieval_option_fmt.vimwiki = '%%%% %s'
<
This example will instead insert "%% name: {name}" for new blocks. Note that
in the example above, the "%" characters are escaped with a 2nd "%" character.
vim:tw=78:ts=8:noet:ft=help:norl:
================================================
FILE: plugin/medieval.vim
================================================
if get(g:, 'loaded_medieval')
finish
endif
let g:loaded_medieval = 1
command! -bang -range -nargs=? EvalBlock
\ if <range> > 0 |
\ call medieval#evalrange(<line1>, <line2>, <bang>0 ? 'self' : <q-args>) |
\ elseif <bang>0 |
\ call medieval#eval('self') |
\ else |
\ call medieval#eval(<q-args>) |
\ endif
nnoremap <silent> <Plug>(medieval-eval) :<C-U>call medieval#eval()<CR>
================================================
FILE: run-test.sh
================================================
#!/bin/sh
cd "$(dirname "$0")"
if [ ! -d test/vader.vim ]; then
git clone --depth 1 https://github.com/junegunn/vader.vim test/vader.vim
fi
vim --not-a-term -Nu test/vimrc '+Vader! test/*.vader' 2>&1 | \
perl -pe 's/\e[\[\]>][0-9;?]*[a-zA-Z]//g; s/\e[>=]//g' | \
grep -E '(^Vader|^Starting|^ Starting|^ \(|^ Success|^Success|^Elapsed)'
================================================
FILE: test/medieval.vader
================================================
Before:
let g:medieval_langs = ['sh']
" === :EvalBlock! — target self (doc line 65-79) ===
Given markdown (EvalBlock! replaces block content):
```sh
echo "hello"
```
Execute (EvalBlock! replaces block in-place):
2
EvalBlock!
Then (Output replaces code block content):
AssertEqual 'hello', getline(2)
AssertEqual '```', getline(3)
" === Named target block (doc line 23-44) ===
Given markdown (Output sent to named target block):
<!-- target: result -->
```sh
echo "computed"
```
<!-- name: result -->
```
```
Execute (Eval source block with named target):
3
EvalBlock
Then (Output written to named target block):
AssertEqual 'computed', getline(8)
" === EvalBlock from destination block redirects to source ===
Given markdown (EvalBlock in destination block evaluates its source):
<!-- target: dest -->
```sh
echo "from source"
```
<!-- name: dest -->
```
old output
```
Execute (Cursor in destination block):
8
EvalBlock
Then (Source block was evaluated, output written to destination):
AssertEqual 'from source', getline(8)
" === :EvalBlock {target} — explicit target argument (doc line 65) ===
Given markdown (Explicit target argument overrides header):
```sh
echo "override"
```
<!-- name: dest -->
```
```
Execute (Explicit target argument):
2
EvalBlock dest
Then (Output sent to explicit target, not header target):
AssertEqual 'override', getline(7)
" === Info string formats (doc line 81-102) ===
Given markdown (Info string with curly-dot: {.lang}):
```{.sh}
echo "dotlang"
```
Execute (EvalBlock! with {.lang} fence):
2
EvalBlock!
Then (Language parsed from {.lang} format):
AssertEqual 'dotlang', getline(2)
Given markdown (Info string with curly: {lang}):
```{sh}
echo "curlylang"
```
Execute (EvalBlock! with {lang} fence):
2
EvalBlock!
Then (Language parsed from {lang} format):
AssertEqual 'curlylang', getline(2)
Given markdown (Info string with extra attributes):
``` {.sh .numberLines #my-id}
echo "attrs"
```
Execute (EvalBlock! with extra attributes):
2
EvalBlock!
Then (Language parsed despite extra attributes):
AssertEqual 'attrs', getline(2)
" === Tilde fences (doc line 104) ===
" SKIPPED: ~~~sh resolves end pattern to ~~~, but ~ is a special regex atom
" in Vim (last substitute string). With no prior :s, this gives E33.
" This is a pre-existing bug in the fence end-pattern handling.
" === Require — recursive dependencies (doc line 179-201) ===
Given markdown (Recursive require chain):
<!-- name: first_name -->
```sh
first_name="Gregory"
```
<!-- name: full_name, require: first_name -->
```sh
full_name="$first_name Anders"
```
<!-- target: greeting, require: full_name -->
```sh
echo "Hi, my name is $full_name"
```
Execute (Eval block with recursive require chain):
13
EvalBlock
Then (All dependencies resolved recursively):
AssertEqual 'Hi, my name is Gregory Anders', getline(18)
" === Language alias (doc line 296-302) ===
Given markdown (Language alias sh=bash):
```sh
echo "aliased"
```
Before:
let g:medieval_langs = ['sh=bash']
Execute (EvalBlock! with aliased language):
2
EvalBlock!
Then (sh executed via bash alias):
AssertEqual 'aliased', getline(2)
After:
let g:medieval_langs = ['sh']
" === Register target (doc line 20-21) ===
Before:
let g:medieval_langs = ['sh']
Given markdown (Register target stores output in register):
```sh
echo "in register"
```
Execute (EvalBlock to register @a):
2
EvalBlock @a
Then (Output stored in register a):
AssertEqual "in register\n", getreg('a')
" === File target (doc line 46-54) ===
Before:
let g:medieval_langs = ['sh']
if filereadable('_medieval_test_output.txt')
throw 'Refusing to run: _medieval_test_output.txt already exists in working directory'
endif
Given markdown (File target writes output to file):
<!-- target: ./_medieval_test_output.txt -->
```sh
echo "file output"
```
Execute (EvalBlock with file target):
3
EvalBlock
Then (Output written to file):
Assert filereadable('_medieval_test_output.txt'), 'Output file should exist'
AssertEqual ['file output'], readfile('_medieval_test_output.txt')
After:
call delete('_medieval_test_output.txt')
" === Tangle (doc line 203-236) ===
Before:
let g:medieval_langs = ['sh']
if filereadable('_medieval_test_tangle.sh')
throw 'Refusing to run: _medieval_test_tangle.sh already exists in working directory'
endif
Given markdown (Tangle writes source to file before executing):
<!-- tangle: _medieval_test_tangle.sh -->
```sh
echo "tangled"
```
Execute (EvalBlock! with tangle option):
3
EvalBlock!
Then (Source tangled to file and executed):
Assert filereadable('_medieval_test_tangle.sh'), 'Tangle file should exist'
AssertEqual ['echo "tangled"'], readfile('_medieval_test_tangle.sh')
AssertEqual 'tangled', getline(3)
After:
call delete('_medieval_test_tangle.sh')
" === Range support ===
Before:
let g:medieval_langs = ['sh']
Given markdown (Two blocks both targeting self):
<!-- target: self -->
```sh
echo "first"
```
<!-- target: self -->
```sh
echo "second"
```
Execute (Range eval replaces all blocks):
%EvalBlock!
Then (Both blocks replaced):
AssertEqual 'first', getline(3)
AssertEqual 'second', getline(8)
Given markdown (Partial range evaluates only covered blocks):
<!-- target: self -->
```sh
echo "in range"
```
<!-- target: self -->
```sh
echo "also in range"
```
<!-- target: self -->
```sh
echo "out of range"
```
Execute (Range covers only first two blocks):
1,10EvalBlock!
Then (First two blocks evaluated, third unchanged):
AssertEqual 'in range', getline(3)
AssertEqual 'also in range', getline(8)
AssertEqual 'echo "out of range"', getline(13)
Given markdown (No blocks in range):
This is just text.
No code blocks here.
```sh
echo "hello"
```
Execute (Range covers only text lines):
1,2EvalBlock
Then (Buffer is unchanged):
AssertEqual 'This is just text.', getline(1)
AssertEqual 'No code blocks here.', getline(2)
AssertEqual 'echo "hello"', getline(5)
Given markdown (Named target block):
<!-- target: output -->
```sh
echo "result"
```
<!-- name: output -->
```
```
Execute (Range eval with named target):
%EvalBlock
Then (Named target block populated with output):
AssertEqual 'result', getline(8)
Given markdown (Closing fences not treated as blocks):
```sh
echo "one"
```
```sh
echo "two"
```
Execute (Range eval only evaluates opening fences):
%EvalBlock!
Then (Both blocks evaluated correctly):
AssertEqual 'one', getline(2)
AssertEqual '```', getline(3)
AssertEqual 'two', getline(6)
AssertEqual '```', getline(7)
" === Range skips named output blocks ===
Given markdown (Range skips named blocks that are output destinations):
<!-- target: out -->
```sh
echo "source"
```
<!-- name: out -->
```sh
old output
```
Execute (Range eval should only run the source block):
%EvalBlock
Then (Source block output written to named block, named block not executed):
AssertEqual 'source', getline(8)
================================================
FILE: test/vimrc
================================================
set nocompatible
filetype off
set runtimepath+=test/vader.vim
set runtimepath+=.
filetype plugin indent on
let g:medieval_langs = ['sh']
let g:medieval_sync = 1
gitextract_t68na24_/
├── .github/
│ └── workflows/
│ └── test.yml
├── LICENSE
├── README.md
├── autoload/
│ └── medieval.vim
├── doc/
│ └── medieval.txt
├── plugin/
│ └── medieval.vim
├── run-test.sh
└── test/
├── medieval.vader
└── vimrc
Condensed preview — 9 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (37K chars).
[
{
"path": ".github/workflows/test.yml",
"chars": 273,
"preview": "name: Test\non:\n push:\n branches: [master]\n pull_request:\njobs:\n test:\n runs-on: ubuntu-latest\n steps:\n "
},
{
"path": "LICENSE",
"chars": 1068,
"preview": "MIT License\n\nCopyright (c) 2020 Greg Anders\n\nPermission is hereby granted, free of charge, to any person obtaining a cop"
},
{
"path": "README.md",
"chars": 1887,
"preview": "# vim-medieval\n\nEvaluate Markdown code blocks within Vim.\n\n[](https://as"
},
{
"path": "autoload/medieval.vim",
"chars": 11397,
"preview": "const s:fences = [#{start: '\\([`~]\\{3,}\\)\\s*\\%({\\s*\\.\\?\\)\\?\\(\\a\\+\\)\\?', end: '\\1', lang: 2,}, #{start: '\\$\\$'}]\nlet s:op"
},
{
"path": "doc/medieval.txt",
"chars": 11383,
"preview": "*medieval.txt* Evaluate Markdown code blocks in Vim\n\nAuthor: Gregory Anders <greg@gpanders.com>\nRepo: https://githu"
},
{
"path": "plugin/medieval.vim",
"chars": 471,
"preview": "if get(g:, 'loaded_medieval')\n finish\nendif\nlet g:loaded_medieval = 1\n\ncommand! -bang -range -nargs=? EvalBlock\n "
},
{
"path": "run-test.sh",
"chars": 349,
"preview": "#!/bin/sh\ncd \"$(dirname \"$0\")\"\n\nif [ ! -d test/vader.vim ]; then\n git clone --depth 1 https://github.com/junegunn/vader"
},
{
"path": "test/medieval.vader",
"chars": 7221,
"preview": "Before:\n let g:medieval_langs = ['sh']\n\n\" === :EvalBlock! — target self (doc line 65-79) ===\n\nGiven markdown (EvalBlock"
},
{
"path": "test/vimrc",
"chars": 161,
"preview": "set nocompatible\nfiletype off\nset runtimepath+=test/vader.vim\nset runtimepath+=.\nfiletype plugin indent on\nlet g:medieva"
}
]
About this extraction
This page contains the full source code of the gpanders/vim-medieval GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 9 files (33.4 KB), approximately 9.5k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.