~axvr/ascribe.vim unlisted

269210b6c5e75a8ee5c14ba56953b1cb7c83e7f5 — Alex Vear 5 months ago 3fcfc71
Make Ascribe behaviour user-modifiable and easily extendable

This change adds a 'b:attributes' variable to each buffer. This variable
contains the list of attributes reported by Git for that file.

Also introduced is the 'g:ascribe_handlers' dictionary. This dictionary
defines the attributes which should be checked (as keys) and maps them
to an appropriate attribute handler (given as a 'Funcref').
4 files changed, 115 insertions(+), 81 deletions(-)

M .gitattributes
M autoload/ascribe.vim
A autoload/ascribe/handlers.vim
M plugin/ascribe.vim
M .gitattributes => .gitattributes +2 -2
@@ 1,2 1,2 @@
*	text=auto final-newline eol=lf
*.vim	expand-tab tab-stop=4 trim-trailing-whitespace
*           text=auto final-newline eol=lf
*.vim       expand-tab tab-stop=4 trim-trailing-whitespace

M autoload/ascribe.vim => autoload/ascribe.vim +22 -77
@@ 1,4 1,4 @@
" Ascribe.vim - An alternative to EditorConfig.
" Ascribe.vim -- An alternative to EditorConfig.
"
" Written in 2019 by Alex Vear <av@axvr.io>
"


@@ 11,99 11,44 @@
" along with this software. If not, see
" <http://creativecommons.org/publicdomain/zero/1.0/>.

function ascribe#set_up_buffer(file)
    let l:attrs = s:getAttrs(['expand-tab', 'tab-stop', 'final-newline',
                \ 'line-length', 'trim-trailing-whitespace', 'eol',
                \ 'binary'], a:file)
function ascribe#configure_buffer(file)
    let b:attributes = s:get_attributes(keys(g:ascribe_handlers), a:file)

    if empty(l:attrs)
        return 1
    endif

    " Indentation
    if l:attrs['tab-stop'] !=# 'unspecified'
        call s:setOptVal(['softtabstop', 'shiftwidth', 'tabstop'], l:attrs['tab-stop'])
    endif

    let l:et = l:attrs['expand-tab']
    if l:et !=# 'unspecified'
        if l:et ==# 'unset'
            call s:setOptVal(['softtabstop', 'shiftwidth'], &l:tabstop)
        endif
        call s:setOptBool(['expandtab'], l:et)
    endif

    " Max line length
    call s:setOptVal(['textwidth'], l:attrs['line-length'])

    " EOL - End-of-line character(s) to use
    let l:eol = l:attrs['eol']
    if l:eol ==? 'lf'
        call s:setOptVal(['ff'], 'unix')
    elseif l:eol ==? 'crlf'
        call s:setOptVal(['ff'], 'dos')
    endif

    " EOF - Insert final newline
    call s:setOptBool(['fixeol'], l:attrs['final-newline'])

    " Binary mode
    call s:setOptBool(['binary', 'readonly'], l:attrs['binary'])

    " Trim trailing whitespace
    if l:attrs['trim-trailing-whitespace'] ==# 'set'
        exec 'autocmd! ascribe BufWritePre <buffer> call s:trim_trailing_whitespace()'
    endif
    for l:attr in keys(b:attributes)
        call g:ascribe_handlers[l:attr](b:attributes[l:attr])
    endfor
endfunction

function <SID>getAttrs(attrs, file)
function <SID>get_attributes(attrs, file)
    let l:cli_args = join(map(copy(a:attrs), '"\"" . v:val .  "\""'), ' ')
    let l:result = systemlist('git check-attr ' . l:cli_args . ' -- ' . shellescape(a:file))

    let l:attr_dict = {}

    " Check if not in a Git repo
    if v:shell_error
        return {}
        return l:attr_dict
    endif

    let l:attr_dict = {}
    let l:itt = 0
    let l:item = 0

    for l:a in a:attrs
        let l:match = matchlist(l:result[l:itt], '\m\C: \([0-9a-zA-Z_.-]\+\)$')
        let l:match = matchlist(l:result[l:item], '\m\C: \([0-9a-zA-Z_.-]\+\)$')

        let l:item = l:item + 1

        if len(l:match[1]) == 0
        let l:value = l:match[1]

        if len(l:value) == 0 || l:value ==# 'unspecified'
            continue
        elseif l:value ==# 'set'
            let l:value = 1
        elseif l:value ==# 'unset'
            let l:value = 0
        endif

        let l:attr_dict[l:a] = l:match[1]
        let l:itt = l:itt + 1
        let l:attr_dict[l:a] = l:value
    endfor

    return l:attr_dict
endfunction

function <SID>setOptBool(opts, attr)
    for l:opt in a:opts
        if a:attr ==# 'set'
            exec 'silent! setlocal ' . l:opt
        elseif a:attr ==# 'unset'
            exec 'silent! setlocal no' . l:opt
        endif
    endfor
endfunction

function <SID>setOptVal(opts, attr)
    if a:attr !=# 'unspecified'
        for l:opt in a:opts
            exec 'silent! setlocal ' . l:opt . '=' . a:attr
        endfor
    endif
endfunction

function <SID>trim_trailing_whitespace()
    normal mz
    normal Hmy
    %s/\s\+$//e
    normal 'yz<CR>
    normal `z
endfunction

A autoload/ascribe/handlers.vim => autoload/ascribe/handlers.vim +77 -0
@@ 0,0 1,77 @@
" Ascribe.vim -- An alternative to EditorConfig.
"
" Written in 2019 by Alex Vear <av@axvr.io>
"
" To the extent possible under law, the author(s) have dedicated all
" copyright and related and neighboring rights to this software to the
" public domain worldwide. This software is distributed without any
" warranty.
"
" You should have received a copy of the CC0 Public Domain Dedication
" along with this software. If not, see
" <http://creativecommons.org/publicdomain/zero/1.0/>.

function ascribe#handlers#binary(value) dict
    call s:set_bool_opt(['binary', 'readonly'], a:value)
endfunction

" EOF - Insert final newline
function ascribe#handlers#final_newline(value) dict
    call s:set_bool_opt(['fixeol'], a:value)
endfunction

" EOL - End-of-line character(s) to use
function ascribe#handlers#eol(eol) dict
    if a:eol ==? 'lf'
        call s:set_val_opt(['ff'], 'unix')
    elseif a:eol ==? 'crlf'
        call s:set_val_opt(['ff'], 'dos')
    endif
endfunction

" Max line length
function ascribe#handlers#line_length(length) dict
    call s:set_val_opt(['textwidth'], a:length)
endfunction

function ascribe#handlers#tab_stop(width) dict
    call s:set_val_opt(['softtabstop', 'shiftwidth', 'tabstop'], a:width)
endfunction

function ascribe#handlers#expand_tab(et) dict
    if !a:et
        if has_key(b:attributes, 'tab-stop')
            call self['tab-stop'](b:attributes['tab-stop'])
        else
            call self['tab-stop'](&l:tabstop)
        endif
    endif
    call s:set_bool_opt(['expandtab'], a:et)
endfunction

" Trim trailing whitespace
function ascribe#handlers#trim_whitespace(trim) dict
    if a:trim
        exec 'autocmd! ascribe BufWritePre <buffer> call s:trim_whitespace()'
    endif
endfunction

function <SID>trim_whitespace()
    normal mz
    normal Hmy
    %s/\s\+$//e
    normal 'yz<CR>
    normal `z
endfunction

function <SID>set_bool_opt(opts, set)
    for l:opt in a:opts
        exec 'silent! setlocal ' . (a:set ? '' : 'no') . l:opt
    endfor
endfunction

function <SID>set_val_opt(opts, val)
    for l:opt in a:opts
        exec 'silent! setlocal ' . l:opt . '=' . a:val
    endfor
endfunction

M plugin/ascribe.vim => plugin/ascribe.vim +14 -2
@@ 1,4 1,4 @@
" Ascribe.vim - An alternative to EditorConfig.
" Ascribe.vim -- An alternative to EditorConfig.
"
" Written in 2019 by Alex Vear <av@axvr.io>
"


@@ 11,7 11,19 @@
" along with this software. If not, see
" <http://creativecommons.org/publicdomain/zero/1.0/>.

" FIXME: if Ascribe is loaded from the 'start' dir when using Vim packages
" this variable will not be available from the user's '.vimrc'.
let g:ascribe_handlers = {
    \ 'expand-tab': function('ascribe#handlers#expand_tab'),
    \ 'tab-stop': function('ascribe#handlers#tab_stop'),
    \ 'eol': function('ascribe#handlers#eol'),
    \ 'trim-trailing-whitespace': function('ascribe#handlers#trim_whitespace'),
    \ 'final-newline': function('ascribe#handlers#final_newline'),
    \ 'line-length': function('ascribe#handlers#line_length'),
    \ 'binary': function('ascribe#handlers#binary')
    \ }

augroup ascribe
    autocmd!
    autocmd BufReadPost,BufNewFile * call ascribe#set_up_buffer(@%)
    autocmd BufReadPost,BufNewFile * call ascribe#configure_buffer(@%)
augroup END