Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
7c75e42
Extract logic to find fenced region into matcher object
vitalk Oct 17, 2015
eb1dbd6
Remove unused function
vitalk Oct 17, 2015
bd23c89
Move some utilities into fancy#util module
vitalk Oct 17, 2015
3a45658
Add loader object to autoload available matchers on demand
vitalk Oct 17, 2015
22f9ba0
Load matchers only when loader is defined for required filetype
vitalk Oct 17, 2015
0ff6558
Update loader prototype order
vitalk Oct 17, 2015
1804b65
Remove unused function
vitalk Oct 17, 2015
21ebbae
Use matcher object to preserve region indent level
vitalk Oct 17, 2015
37ced59
Reindent for better readability
vitalk Oct 18, 2015
2c2b8ef
A bunch of comments and improvements
vitalk Oct 18, 2015
8ddebe5
Set required buffer variable during init instead of autocommand
vitalk Oct 18, 2015
25e0c50
Add buffer constructor arguments description
vitalk Oct 18, 2015
db47494
Add arguments description for buffer prototype
vitalk Oct 18, 2015
098d82f
Remove sync method from fancy prototype
vitalk Oct 18, 2015
88f137f
Use buffer object to lookup fancy object
vitalk Oct 18, 2015
039c658
Realing assignments for sake of beauty
vitalk Oct 18, 2015
3c01133
Remove optional arguments from some functions
vitalk Oct 18, 2015
eaa7549
Update loader prototype
vitalk Oct 18, 2015
ce388aa
Update prototype comments
vitalk Oct 18, 2015
850b4d1
Update s:matcher_filetype spec
vitalk Oct 18, 2015
4063cd9
s/save/save_to_cache/
vitalk Oct 19, 2015
54257aa
s/func/path/ in s:loader_is_defined method
vitalk Oct 19, 2015
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
269 changes: 170 additions & 99 deletions autoload/fancy.vim
Original file line number Diff line number Diff line change
Expand Up @@ -24,25 +24,16 @@ fun! s:get_id()
return s:id
endf

fun! s:indent_line(line, indent)
return printf('%*s%s', a:indent, a:indent ? ' ' : '', a:line)
endf

fun! s:dedent_line(line, indent)
return substitute(a:line, '^\s\{'.a:indent.'\}', '', '')
endf

fun! s:indent_lines(lines, indent)
return a:indent < 0
\ ? map(lines, 's:dedent_line(v:val, indent)')
\ : map(lines, 's:indent_line(v:val, indent)')
endf

" }}}
" Buffer prototype {{{

let s:buffer_prototype = {}

" Returns a new buffer instance.
"
" Arguments:
" - buffer number (as per bufnr spec), use current buffer when not set;
" - number of fancy object if any.
fun! s:buffer(...) abort
let buffer = {
\ '#': bufnr(a:0 ? a:1 : '%'),
Expand Down Expand Up @@ -101,12 +92,22 @@ fun! s:buffer_delete() dict abort
endif
endf

" Returns the content of the buffer.
"
" Arguments:
" - the line number to start (read from the beginning if not set);
" - the line number to end (read until the end if not set).
fun! s:buffer_read(...) dict abort
return getbufline(self.name(),
\ a:0 ? a:1 : 1,
\ (a:0 == 2) ? a:2 : '$')
endf

" Write text to the buffer.
"
" Arguments:
" - the optional line number to start with;
" - text to write (as per setline spec).
fun! s:buffer_write(...) dict abort
if empty(a:0)
return
Expand All @@ -120,39 +121,135 @@ endf

" Returns the buffer content with or without indentation.
"
" The arguments are:
" Arguments:
" - the indentation level (dedent buffer when value is negative and indent otherwise)
" - the line number to start (read from the beginning if not set)
" - the line number to end (process until the end if not set)
fun! s:buffer_indent(indent, ...) dict abort
let start_at = a:0 ? a:1 : 1
let end_at = (a:0 > 1) ? a:2 : '$'
return a:indent < 0
\ ? map(self.read(start_at, end_at), 's:dedent_line(v:val, a:indent)')
\ : map(self.read(start_at, end_at), 's:indent_line(v:val, a:indent)')
\ ? map(self.read(start_at, end_at), 'fancy#util#dedent_line(v:val, a:indent)')
\ : map(self.read(start_at, end_at), 'fancy#util#indent_line(v:val, a:indent)')
endf

call s:add_methods('buffer', [
\ 'getvar', 'setvar', 'name', 'delete', 'read', 'write',
\ 'exists', 'spec', 'path', 'fancy_id', 'indent'
\ ])

" }}}
" Matcher prototype {{{

let s:matcher_prototype = {}

fun! s:matcher() abort
let matcher = {
\ 'start_at': 0,
\ 'end_at': 0,
\ 'indent_level': 0,
\ }
call extend(matcher, s:matcher_prototype, 'keep')
return matcher
endf

" Returns the number of the first line of the region.
fun! s:matcher_start_line(...) dict abort
endf

" Returns the number of the last line of the region.
fun! s:matcher_end_line(...) dict abort
endf

" Returns the filetype of the region.
"
" Arguments:
" - the fancy object (can be used to read and extract data from buffer).
fun! s:matcher_filetype(fancy, ...) dict abort
endf

" Find fenced region and save it position if any. Return false if
" no region has been found and true otherwise.
fun! s:matcher_find_region() dict abort
let self.start_at = self.start_line()
let self.indent_level = indent(self.start_at)
let self.end_at = self.end_line()
return (self.start_at != 0 && self.end_at != 0) ? 1 : 0
endf

fun! s:matcher_search_forward(pattern) dict abort
return search(a:pattern, 'cnW')
endf

fun! s:matcher_search_backward(pattern) dict abort
return search(a:pattern, 'bcnW')
endf

call s:add_methods('matcher', [
\ 'filetype', 'start_line', 'end_line', 'find_region',
\ 'search_forward', 'search_backward'
\ ])

" }}}
" Loader prototype {{{

let s:loader_prototype = {}

fun! s:loader() abort
let loader = {
\ 'filetypes': {}
\ }
call extend(loader, s:loader_prototype, 'keep')
return loader
endf

fun! s:loader_load_from_cache(ft) dict abort
return self.filetypes[a:ft]
endf

fun! s:loader_save_to_cache(ft, list) dict abort
let self.filetypes[a:ft] = a:list
endf

fun! s:loader_is_cached(ft) dict abort
return has_key(self.filetypes, a:ft)
endf

fun! s:loader_is_defined(ft) dict abort
let path = 'autoload/fancy/ft/'.a:ft.'.vim'
return !empty(globpath(&rtp, path))
endf

fun! s:loader_load(ft) dict abort
if self.is_cached(a:ft)
return self.load_from_cache(a:ft)

elseif self.is_defined(a:ft)
let matchers = fancy#ft#{a:ft}#matchers()
call self.save_to_cache(a:ft, matchers)
return matchers
endif
endf

call s:add_methods('loader', [
\ 'load', 'load_from_cache', 'save_to_cache', 'is_cached', 'is_defined'
\ ])

" }}}
" Fancy prototype {{{

let s:fancy_prototype = {}

fun! s:fancy() abort
let candidates = s:get_filetype_options(&filetype)
if empty(candidates)
call s:error(printf('%s: no available search options', &filetype))
let matchers = s:loader().load(&filetype)
if empty(matchers)
call s:error(printf('%s: no available matcher', &filetype))
return
endif

let found = 0
for search_options in candidates
let [start_at, end_at] = s:get_region_bounds(search_options)
if start_at != 0 && end_at != 0
for matcher in matchers
if matcher.find_region()
let found = 1
break
endif
Expand All @@ -165,102 +262,93 @@ fun! s:fancy() abort

let fancy = {
\ 'id': s:get_id(),
\ 'options': search_options,
\ 'start_at': start_at,
\ 'end_at': end_at,
\ 'matcher': matcher,
\ 'buffer': s:buffer(),
\ 'indent_level': indent(start_at)
\ }
call extend(fancy, s:fancy_prototype, 'keep')

call add(s:fancy_objects, fancy)
return fancy
endf

fun! s:fancy_sync() dict abort
return s:sync()
endf

fun! s:fancy_filetype() dict abort
let filetype = self.options.filetype(self)
let filetype = self.matcher.filetype(self)
return empty(filetype)
\ ? self.buffer.getvar('&filetype')
\ : filetype
endf

fun! s:fancy_text() dict abort
return self.buffer.indent(
\ -self.indent_level,
\ self.start_at + 1,
\ self.end_at - 1)
\ -self.matcher.indent_level,
\ self.matcher.start_at + 1,
\ self.matcher.end_at - 1)
endf

fun! s:fancy_destroy() dict abort
call remove(s:fancy_objects, index(s:fancy_objects, self))
endf

call s:add_methods('fancy', ['sync', 'filetype', 'text', 'destroy'])
call s:add_methods('fancy', ['filetype', 'text', 'destroy'])


fun! s:lookup_fancy(id)
let found = filter(copy(s:fancy_objects), 'v:val["id"] == a:id')
" Returns fancy object bound to buffer.
fun! s:lookup_fancy(buffer)
let fancy_id = a:buffer.fancy_id()
let found = filter(copy(s:fancy_objects), 'v:val["id"] == fancy_id')
if empty(found)
call s:error('Original buffer does no longer exist! Aborting!')
return
endif
return found[0]
endf

fun! s:get_filetype_options(ft)
if has_key(g:fancy_filetypes, a:ft)
return g:fancy_filetypes[a:ft]
endif
return []
endf

fun! s:search_forward(pattern)
return search(a:pattern, 'cnW')
endf
" }}}
" Fancy public interface {{{

fun! s:search_backward(pattern)
return search(a:pattern, 'bcnW')
fun! fancy#matcher() abort
return s:matcher()
endf

fun! s:get_region_bounds(options)
let start_at = s:search_backward(a:options.start_at)
let end_at = s:search_forward(a:options.end_at)
return [start_at, end_at]
fun! fancy#fancy() abort
return s:fancy()
endf

fun! s:edit()
fun! fancy#init() abort
" Create a new fancy instance for the current buffer. Exit silently when
" fenced region does not found.
let fancy = fancy#fancy()
if (type(fancy) != type({}))
return
endif

" Create a new temporary file and open it in split.
let name = tempname()
exe 'split '.name
let buffer = s:buffer(name, fancy.id)

" Bind buffer to the fancy object and
" - copy fenced region into it;
" - detect and set filetype;
" - ensure the buffer is wiped out when it's no longer displayed
" in a window;
" - mark buffer as nomodified, to prevent warning when trying to close it;
" - disable swap file for the buffer;
" - show buffer in the buffer list;
" - rename buffer according with its spec.
let buffer = s:buffer(name, fancy.id)
call buffer.write(fancy.text())
call buffer.setvar('&ft', fancy.filetype())
call buffer.setvar('&bufhidden', 'wipe')
call buffer.write(fancy.text())

call buffer.setvar('&modified', 0)
call buffer.setvar('&swapfile', 0)
call buffer.setvar('&buflisted', 1)
sil exe 'file '.buffer.spec()
setl nomodified
endf

fun! s:destroy(...)
let bufnr = a:0 ? a:1[0] : '%'
let buffer = s:buffer(bufnr)
let fancy = s:lookup_fancy(buffer.fancy_id())
call fancy.destroy()
endf

fun! s:sync(...)
let bufnr = a:0 ? a:1[0] : '%'
let buffer = s:buffer(bufnr)
let fancy = s:lookup_fancy(buffer.fancy_id())
fun! fancy#sync(bufnr) abort
" Get buffer and related fancy object.
let buffer = s:buffer(a:bufnr)
let fancy = s:lookup_fancy(buffer)

" Go to original buffer.
let winnr = bufwinnr(fancy.buffer.name())
Expand All @@ -269,45 +357,28 @@ fun! s:sync(...)
endif

" Sync any changes.
if (fancy.end_at - fancy.start_at > 1)
exe printf('%s,%s delete _', fancy.start_at + 1, fancy.end_at - 1)
if (fancy.matcher.end_at - fancy.matcher.start_at > 1)
exe printf('%s,%s delete _', fancy.matcher.start_at + 1, fancy.matcher.end_at - 1)
endif
call append(fancy.start_at, buffer.indent(fancy.indent_level))
call append(fancy.matcher.start_at, buffer.indent(fancy.matcher.indent_level))

" Restore the original cursor position.
call setpos('.', fancy.buffer.pos)

" Update start/end block position.
let [fancy.start_at, fancy.end_at] = s:get_region_bounds(fancy.options)
call fancy.matcher.find_region()
endf

fun! s:write(...)
let bufnr = a:0 ? a:1[0] : '%'
sil exe 'write! '.s:buffer(bufnr).path()
setl nomodified
fun! fancy#write(bufnr) abort
let buffer = s:buffer(a:bufnr)
sil exe 'write! '.buffer.path()
call buffer.setvar('&modified', 0)
endf

" }}}
" Funcy public interface {{{

fun! fancy#fancy() abort
return s:fancy()
endf

fun! fancy#edit() abort
return s:edit()
endf

fun! fancy#sync(...) abort
return s:sync(a:000)
endf

fun! fancy#write(...) abort
return s:write(a:000)
endf

fun! fancy#destroy(...) abort
return s:destroy(a:000)
fun! fancy#destroy(bufnr) abort
let buffer = s:buffer(a:bufnr)
let fancy = s:lookup_fancy(buffer)
call fancy.destroy()
endf

" }}}
Loading