1
0
mirror of https://github.com/SpaceVim/SpaceVim.git synced 2025-01-23 07:00:04 +08:00

feat(edit): add grammar checking plugin

This commit is contained in:
wsdjeg 2023-03-28 23:43:55 +08:00
parent 1fdc034d7e
commit 54b1170497
23 changed files with 5106 additions and 0 deletions

View File

@ -102,6 +102,7 @@ function! SpaceVim#layers#edit#plugins() abort
\ [g:_spacevim_root_dir . 'bundle/vim-surround'],
\ [g:_spacevim_root_dir . 'bundle/vim-repeat'],
\ [g:_spacevim_root_dir . 'bundle/vim-emoji'],
\ [g:_spacevim_root_dir . 'bundle/vim-grammarous', {'merged' : 0}],
\ [g:_spacevim_root_dir . 'bundle/vim-expand-region', { 'loadconf' : 1}],
\ [g:_spacevim_root_dir . 'bundle/vim-textobj-user'],
\ [g:_spacevim_root_dir . 'bundle/vim-textobj-indent'],

5
bundle/README.md vendored
View File

@ -7,6 +7,7 @@ In `bundle/` directory, there are two kinds of plugins: forked plugins without c
- [Changed plugin:](#changed-plugin)
- [No changed plugins](#no-changed-plugins)
- [`core` layer](#core-layer)
- [`edit` layer](#edit-layer)
- [`lang#ruby` layer](#langruby-layer)
- [`lang#python` layer](#langpython-layer)
- [`lang#liquid` layer](#langliquid-layer)
@ -58,6 +59,10 @@ This plugins are changed based on a specific version of origin plugin.
- [nerdtree@fc85a6f07](https://github.com/preservim/nerdtree/tree/fc85a6f07c2cd694be93496ffad75be126240068)
### `edit` layer
- [rhysd/vim-grammarous@db46357](https://github.com/rhysd/vim-grammarous/tree/db46357465ce587d5325e816235b5e92415f8c05)
#### `lang#ruby` layer
- [vim-ruby@55335f261](https://github.com/vim-ruby/vim-ruby/tree/55335f2614f914b117f02995340886f409eddc02)

2
bundle/vim-grammarous/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
misc
doc/tags

267
bundle/vim-grammarous/README.md vendored Normal file
View File

@ -0,0 +1,267 @@
vim-grammarous
==============
vim-grammarous is a powerful grammar checker for Vim. Simply do `:GrammarousCheck` to see the powerful checking.
This plugin automatically downloads [LanguageTool](https://www.languagetool.org/), which requires Java 8+.
This plugin can use job feature on Vim 8.0.27 (or later) or Neovim. It enables asynchronous command execution so you don't need to
be blocked until the check has been done on Vim8+ or Neovim.
![demo screen cast](https://github.com/rhysd/ss/blob/master/vim-grammarous/demo.gif?raw=true)
## Commands
```
:[range]GrammarousCheck [--lang={lang}] [--(no-)preview] [--(no-)comments-only]
```
Execute the grammar checker for current buffer (when `[range]` is specified, the target is a text in the range).
1. It makes LanguageTool check grammar (It takes a while)
2. It highlights the locations of detected grammar errors
3. When you move the cursor to a location of an error, it automatically shows the error with the information window (named `[Grammarous]`).
Please do `:GrammarousCheck --help` to show more detail about the command.
```
:GrammarousReset
```
Reset the current check.
## Mappings
### Local mappings in the information window
You can use some mappings in the information window, which is opened to show the detail of an error when the cursor moves to the error.
| Mappings | Description |
| -------- |:---------------------------------------------- |
| `q` | Quit the info window |
| `<CR>` | Move to the location of the error |
| `f` | Fix the error __automatically__ |
| `r` | Remove the error without fix |
| `R` | Disable the grammar rule in the checked buffer |
| `n` | Move to the next error's location |
| `p` | Move to the previous error's location |
| `?` | Show help of the mapping in info window |
### `<Plug>` mappings to execute actions anywhere
The above local mappings are enough to deal with grammar errors.
However, for a more convenient use, vim-grammarous provides the following global mappings to enable
using all grammarous actions globally within vim. This might be beneficial, as the standard mappings
only work within the info window, which loses focus after every action.
By mapping the actions listed below to your favorite shortcuts, it is possible to map all actions
that work within the info window, to work globally within vim. This is done via `:nmap` and an example
for a mapping would be `:nmap <F5> (grammarous-move-to-next-error)`.
| Mappings | Description |
| ------------------------------------------- |:---------------------------------------------------- |
| `<Plug>(grammarous-move-to-info-window)` | Move the cursor to the info window |
| `<Plug>(grammarous-open-info-window)` | Open the info window for the error under the cursor |
| `<Plug>(grammarous-reset)` | Reset the current check |
| `<Plug>(grammarous-fixit)` | Fix the error under the cursor automatically |
| `<Plug>(grammarous-fixall)` | Fix all the errors in a current buffer automatically |
| `<Plug>(grammarous-close-info-window)` | Close the information window from checked buffer |
| `<Plug>(grammarous-remove-error)` | Remove the error under the cursor |
| `<Plug>(grammarous-disable-rule)` | Disable the grammar rule under the cursor |
| `<Plug>(grammarous-move-to-next-error)` | Move cursor to the next error |
| `<Plug>(grammarous-move-to-previous-error)` | Move cursor to the previous error |
### Operator mappings
Operator mapping checks grammatical errors in the extent which the text object specifies.
This mapping is available when [vim-operator-user](https://github.com/kana/vim-operator-user) is installed.
| Mappings | Description |
| ----------------------------- |:-------------------------------------- |
| `<Plug>(operator-grammarous)` | Execute grammar check to a text object |
### `grammarous` unite.vim source
If you are [unite.vim](https://github.com/Shougo/unite.vim) user, `grammarous` unite source is available to look and search the error list incrementally.
To the candidates of the list, you can do the actions which are the same as ones in the info window. (`fixit`, `remove error` and `disable rule`)
Execute below command in the buffer already checked or you want to check.
```
:Unite grammarous
```
### `grammarous` denite.nvim source
For [denite.nvim](https://github.com/Shougo/denite.nvim) users, `grammarous` denite source is available. Note that the kind is currently set to `file`,
which means that actions a user can use are limited to open(jump), preview, etc. Execute below command in the buffer already checked.
```
:Denite grammarous
```
## Fix examples
- [vim-themis](https://github.com/rhysd/vim-themis/commit/b2f838b29f47180ccee50488e01d6774a21d0c03)
- [unite.vim](https://github.com/rhysd/unite.vim/commit/5716eac38781e7a233c98f2a3d7aee8909326791)
- [vim-quickrun](https://github.com/rhysd/vim-quickrun/commit/236c753e0572266670d176e667054d55ad52a3f3)
- [neosnippet.vim](https://github.com/rhysd/neosnippet/commit/c72e26e50ccf53f9d66a31fd9d70696c85c62873)
## FAQ
### How do I check comments only in source code by default?
Please use `g:grammarous#default_comments_only_filetypes`.
For example, below setting makes grammar checker check comments only except for markdown and vim help.
```vim
let g:grammarous#default_comments_only_filetypes = {
\ '*' : 1, 'help' : 0, 'markdown' : 0,
\ }
```
### How are rules added to the default rule set?
Please use `g:grammarous#enabled_rules` to enable additional rules. The value is dictionary whose keys
are a filetype (`*` means 'any') and whose values are a list of rule names.
For example, below setting enables `PASSIVE_VOICE` rule in all filetypes.
```vim
let g:grammarous#enabled_rules = {'*' : ['PASSIVE_VOICE']}
```
### Some rules annoy me.
Please use `g:grammarous#disabled_rules` to disable specific rules.
For example, below setting disables some rules for each filetype. `*` means all filetypes, `help` means vim help.
```vim
let g:grammarous#disabled_rules = {
\ '*' : ['WHITESPACE_RULE', 'EN_QUOTES'],
\ 'help' : ['WHITESPACE_RULE', 'EN_QUOTES', 'SENTENCE_WHITESPACE', 'UPPERCASE_SENTENCE_START'],
\ }
```
The rule names are displayed in Vim command line when you disable the rule in the info window or by `<Plug>(grammarous-disable-rule)`.
### How are categories added to the default rule set?
Please use `g:grammarous#enabled_categories` to enable additional categories. The value is dictionary whose keys
are a filetype (`*` means 'any') and whose values are a list of categories names.
For example, below setting enables `PASSIVE_VOICE` rule in all filetypes.
```vim
let g:grammarous#enabled_categories = {'*' : ['PUNCTUATION']}
```
### Some categories annoy me.
Please use `g:grammarous#disabled_categories` to disable specific categories.
For example, below setting disables some categories for each filetype. `*` means all filetypes, `help` means vim help.
```vim
let g:grammarous#disabled_categories = {
\ '*' : ['PUNCTUATION'],
\ 'help' : ['PUNCTUATION', 'TYPOGRAPHY'],
\ }
```
The category names are displayed in Vim command line when you disable the category in the info window or by `<Plug>(grammarous-disable-category)`.
### How do I use this plugin with vim's spelllang?
Please use `g:grammarous#use_vim_spelllang`. Default 0, to enable 1.
### I want to use above `<Plug>` mappings only after checking.
`on_check` and `on_reset` are available.
For example, below setting defines `<C-n>` and `<C-p>` mappings as buffer-local mappings when the check has been completed.
They are cleared when the check is reset.
```vim
let g:grammarous#hooks = {}
function! g:grammarous#hooks.on_check(errs) abort
nmap <buffer><C-n> <Plug>(grammarous-move-to-next-error)
nmap <buffer><C-p> <Plug>(grammarous-move-to-previous-error)
endfunction
function! g:grammarous#hooks.on_reset(errs) abort
nunmap <buffer><C-n>
nunmap <buffer><C-p>
endfunction
```
### I want to use system global LanguageTool command
`g:grammarous#languagetool_cmd` is available for the purpose.
If some command is set to `g:grammarous#languagetool_cmd` in your `.vimrc`, vim-grammarous does not install
its own LanguageTool jar and use the command to run LanguageTool.
```vim
let g:grammarous#languagetool_cmd = 'languagetool'
```
### I want to see the first error in an information window soon after `:GrammarousCheck`
Please set `g:grammarous#show_first_error` to `1`. It opens an information window after `:GrammarousCheck` immediately when some error detected.
### I want to use a location list to jump among errors
Please set `g:grammarous#use_location_list` to `1`. It sets all grammatical errors to location list.
This variable is set to `0` by default to avoid conflicts of location list usage with other plugins.
## Automatic installation
This plugin attempts to install [LanguageTool](https://www.languagetool.org/) using `curl` or `wget` command at first time.
If it fails, you should install it manually. Please download zip file of LanguageTool and extract it to `path/to/vim-grammarous/misc`.
## Requirements
- Java8+ (required)
- [vimproc.vim](https://github.com/Shougo/vimproc.vim) (optional for Vim 8.0.25 or earlier on Windows)
- [unite.vim](https://github.com/Shougo/unite.vim) (optional)
- [vim-operator-user](https://github.com/kana/vim-operator-user) (optional)
## Future
- __Ignore specific regions__ : Enable to specify the region which vim-grammarous should not check. It is helpful for GFM's fenced code blocks.
- __Incremental grammarous check__ : Check only the sentences you input while starting from entering and leaving insert mode.
## Contribution
If you find some bugs, please report it to [issues page](https://github.com/rhysd/vim-grammarous/issues).
Pull requests are welcome. None of them is too short.
## License
Copyright (c) 2014 rhysd
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.

View File

@ -0,0 +1,798 @@
let s:save_cpo = &cpo
set cpo&vim
let s:V = vital#grammarous#new()
let s:XML = s:V.import('Web.XML')
let s:O = s:V.import('OptionParser')
let s:P = s:V.import('Process')
let s:is_cygwin = has('win32unix')
let s:is_windows = has('win32') || has('win64')
let s:job_is_available = has('job') && has('patch-8.0.0027')
let s:grammarous_root = fnamemodify(expand('<sfile>'), ':p:h:h')
let g:grammarous#jar_dir = get(g:, 'grammarous#jar_dir', s:grammarous_root . '/misc')
let g:grammarous#jar_url = get(g:, 'grammarous#jar_url', 'https://www.languagetool.org/download/LanguageTool-stable.zip')
let g:grammarous#java_cmd = get(g:, 'grammarous#java_cmd', 'java')
let g:grammarous#default_lang = get(g:, 'grammarous#default_lang', 'en')
let g:grammarous#use_vim_spelllang = get(g:, 'grammarous#use_vim_spelllang', 0)
let g:grammarous#info_window_height = get(g:, 'grammarous#info_window_height', 10)
let g:grammarous#info_win_direction = get(g:, 'grammarous#info_win_direction', 'botright')
let g:grammarous#use_fallback_highlight = get(g:, 'grammarous#use_fallback_highlight', !exists('*matchaddpos'))
let g:grammarous#enabled_rules = get(g:, 'grammarous#enabled_rules', {})
let g:grammarous#disabled_rules = get(g:, 'grammarous#disabled_rules', {'*' : ['WHITESPACE_RULE', 'EN_QUOTES']})
let g:grammarous#enabled_categories = get(g:, 'grammarous#enabled_categories', {})
let g:grammarous#disabled_categories = get(g:, 'grammarous#disabled_categories', {})
let g:grammarous#default_comments_only_filetypes = get(g:, 'grammarous#default_comments_only_filetypes', {'*' : 0})
let g:grammarous#enable_spell_check = get(g:, 'grammarous#enable_spell_check', 0)
let g:grammarous#move_to_first_error = get(g:, 'grammarous#move_to_first_error', 1)
let g:grammarous#hooks = get(g:, 'grammarous#hooks', {})
let g:grammarous#languagetool_cmd = get(g:, 'grammarous#languagetool_cmd', '')
let g:grammarous#show_first_error = get(g:, 'grammarous#show_first_error', 0)
let g:grammarous#use_location_list = get(g:, 'grammarous#use_location_list', 0)
highlight default link GrammarousError SpellBad
highlight default link GrammarousInfoError ErrorMsg
highlight default link GrammarousInfoSection Keyword
highlight default link GrammarousInfoHelp Special
augroup pluging-rammarous-highlight
autocmd ColorScheme * highlight default link GrammarousError SpellBad
autocmd ColorScheme * highlight default link GrammarousInfoError ErrorMsg
autocmd ColorScheme * highlight default link GrammarousInfoSection Keyword
autocmd ColorScheme * highlight default link GrammarousInfoHelp Special
augroup END
function! s:get_SID() abort
return matchstr(expand('<sfile>'), '<SNR>\d\+_\zeget_SID$')
endfunction
let s:SID = s:get_SID()
delfunction s:get_SID
function! grammarous#_import_vital_modules()
return [s:XML, s:O, s:P]
endfunction
function! grammarous#error(...)
echohl ErrorMsg
try
if a:0 > 1
let msg = 'vim-grammarous: ' . call('printf', a:000)
else
let msg = 'vim-grammarous: ' . a:1
endif
for l in split(msg, "\n")
echomsg l
endfor
finally
echohl None
endtry
endfunction
function! s:delete_jar_dir() abort
if !isdirectory(g:grammarous#jar_dir)
return
endif
let dir = g:grammarous#jar_dir
if s:is_cygwin
let dir = s:cygpath(dir)
endif
if dir ==# '' || !isdirectory(dir)
call grammarous#error("Directory '%s' does not exist", dir)
return
endif
if s:is_windows && !s:is_cygwin
let cmd = 'rmdir /s /q ' . dir
else
let cmd = 'rm -rf ' . dir
endif
let out = system(cmd)
if v:shell_error
call grammarous#error("Cannot remove the directory '%s': %s", dir, out)
return
endif
echomsg 'Deleted ' . dir
unlet! s:jar_file
endfunction
function! s:find_jar(dir)
return findfile('languagetool-commandline.jar', a:dir . '/**')
endfunction
function! s:prepare_jar(dir)
let jar = s:find_jar(a:dir)
if jar ==# ''
if grammarous#downloader#download(a:dir)
let jar = s:find_jar(a:dir)
endif
endif
return fnamemodify(jar, ':p')
endfunction
function! s:find_jar_path()
if exists('s:jar_file')
return s:jar_file
endif
if !executable(g:grammarous#java_cmd)
call grammarous#error('"java" command not found. Please install Java 8+')
return ''
endif
" TODO:
" Check java version
let jar = s:prepare_jar(g:grammarous#jar_dir)
if jar ==# ''
call grammarous#error('Failed to get LanguageTool')
return ''
endif
if s:is_cygwin
let jar = s:cygpath(jar)
endif
let s:jar_file = jar
return jar
endfunction
function! s:cygpath(path) abort
if !executable('cygpath')
return a:path
endif
" On Cygwin environment, paths should be converted with cygpath.
" /cygdrive/c/... -> C:/...
" https://github.com/rhysd/vim-grammarous/issues/30
let converted = substitute(s:P.system('cygpath -aw ' . a:path), '\n\+$', '', '')
if s:P.get_last_status()
return a:path
endif
return converted
endfunction
function! s:make_text(text)
if type(a:text) == type('')
return a:text
else
return join(a:text, "\n")
endif
endfunction
function! s:set_errors_to_location_list() abort
let f = expand('%:p')
let saved_efm = &l:errorformat
try
setlocal errorformat=%f:%l:%c:%m
let lines = map(copy(b:grammarous_result), '
\ printf("%s:%s:%s:%s [%s]", f, v:val.fromy + 1, v:val.fromx + 1, v:val.msg, v:val.category)
\')
lgetexpr lines
finally
let &l:errorformat = saved_efm
endtry
endfunction
function! s:set_errors_from_xml_string(xml) abort
let b:grammarous_result = grammarous#get_errors_from_xml(s:XML.parse(substitute(a:xml, "\n", '', 'g')))
let parsed = s:last_parsed_options
if s:is_comment_only(parsed['comments-only'])
call filter(b:grammarous_result, 'synIDattr(synID(v:val.fromy+1, v:val.fromx+1, 0), "name") =~? "comment"')
endif
redraw!
if empty(b:grammarous_result)
echomsg 'Yay! No grammatical errors detected.'
return
endif
let len = len(b:grammarous_result)
echomsg printf('Detected %d grammatical error%s', len, len > 1 ? 's' : '')
call grammarous#highlight_errors_in_current_buffer(b:grammarous_result)
if parsed['move-to-first-error']
call cursor(b:grammarous_result[0].fromy+1, b:grammarous_result[0].fromx+1)
endif
if g:grammarous#enable_spell_check
let s:saved_spell = &l:spell
setlocal spell
endif
if g:grammarous#use_location_list
call s:set_errors_to_location_list()
endif
if g:grammarous#show_first_error
call grammarous#create_update_info_window_of(b:grammarous_result)
endif
if has_key(g:grammarous#hooks, 'on_check')
call call(g:grammarous#hooks.on_check, [b:grammarous_result], g:grammarous#hooks)
endif
endfunction
function! s:on_check_done_vim8(channel) abort
let xml = ''
while ch_status(a:channel, {'part' : 'out'}) ==# 'buffered'
let xml .= ch_read(a:channel)
endwhile
if xml ==# ''
return
endif
call s:set_errors_from_xml_string(xml)
endfunction
function! s:on_check_exit_vim8(channel, status) abort
if a:status == 0
return
endif
let err = ''
while ch_status(a:channel, {'part' : 'err'}) ==# 'buffered'
let err .= ch_read(a:channel, {'part' : 'err'})
endwhile
call grammarous#error('Grammar check failed with exit status ' . a:status . ': ' . err)
endfunction
function! s:on_exit_nvim(job, status, event) abort dict
if a:status != 0
call grammarous#error('Grammar check failed: ' . self._stderr)
return
endif
call s:set_errors_from_xml_string(self._stdout)
endfunction
function! s:on_output_nvim(job, lines, event) abort dict
let output = join(a:lines, "\n")
if a:event ==# 'stdout'
let self._stdout .= output
else
let self._stderr .= output
endif
endfunction
function! s:invoke_check(range_start, ...)
if g:grammarous#languagetool_cmd ==# ''
let jar = s:find_jar_path()
if jar ==# ''
return
endif
endif
if a:0 < 1
call grammarous#error('Invalid argument. At least one argument is required.')
return
endif
if g:grammarous#use_vim_spelllang
" Convert vim spelllang to languagetool spelllang
if len(split(&spelllang, '_')) == 1
let lang = split(&spelllang, '_')[0]
elseif len(split(&spelllang, '_')) == 2
let lang = split(&spelllang, '_')[0].'-'.toupper(split(&spelllang, '_')[1])
endif
else
let lang = a:0 == 1 ? g:grammarous#default_lang : a:1
endif
let text = s:make_text(a:0 == 1 ? a:1 : a:2)
let tmpfile = tempname()
execute 'redir! >' tmpfile
let l = 1
while l < a:range_start
silent echo ""
let l += 1
endwhile
silent echon text
redir END
if s:is_cygwin
let tmpfile = s:cygpath(tmpfile)
endif
let cmdargs = printf(
\ '-c %s -l %s --api %s',
\ &fileencoding ? &fileencoding : &encoding,
\ lang,
\ substitute(tmpfile, '\\\s\@!', '\\\\', 'g')
\ )
let disabled_rules = get(g:grammarous#disabled_rules, &filetype, get(g:grammarous#disabled_rules, '*', []))
if !empty(disabled_rules)
let cmdargs = '-d ' . join(disabled_rules, ',') . ' ' . cmdargs
endif
let enabled_rules = get(g:grammarous#enabled_rules, &filetype, get(g:grammarous#enabled_rules, '*', []))
if !empty(enabled_rules)
let cmdargs = '-e ' . join(enabled_rules, ',') . ' ' . cmdargs
endif
let disabled_categories = get(g:grammarous#disabled_categories, &filetype, get(g:grammarous#disabled_categories, '*', []))
if !empty(disabled_categories)
let cmdargs = '--disablecategories ' . join(disabled_categories, ',') . ' ' . cmdargs
endif
let enabled_categories = get(g:grammarous#enabled_categories, &filetype, get(g:grammarous#enabled_categories, '*', []))
if !empty(enabled_categories)
let cmdargs = '--enablecategories ' . join(enabled_categories, ',') . ' ' . cmdargs
endif
if g:grammarous#languagetool_cmd !=# ''
let cmd = printf('%s %s', g:grammarous#languagetool_cmd, cmdargs)
else
let cmd = printf('%s -jar %s %s', g:grammarous#java_cmd, substitute(jar, '\\\s\@!', '\\\\', 'g'), cmdargs)
endif
if s:job_is_available
let job = job_start(cmd, {'close_cb' : s:SID . 'on_check_done_vim8', 'exit_cb' : s:SID . 'on_check_exit_vim8'})
echo 'Grammar check has started with job(' . job . ')...'
return
endif
if has('nvim')
let opts = {
\ 'on_stdout' : function('s:on_output_nvim'),
\ 'on_stderr' : function('s:on_output_nvim'),
\ 'on_exit' : function('s:on_exit_nvim'),
\ '_stdout' : '',
\ '_stderr' : '',
\ }
let job = jobstart(cmd, opts)
echo 'Grammar check has started with job(id: ' . job . ')...'
return
endif
let xml = s:P.system(cmd)
call delete(tmpfile)
if s:P.get_last_status()
call grammarous#error("Command '%s' failed:\n%s", cmd, xml)
return
endif
call s:set_errors_from_xml_string(xml)
endfunction
function! s:sanitize(s)
return substitute(escape(a:s, "'\\"), ' ', '\\_\\s', 'g')
endfunction
function! grammarous#generate_highlight_pattern(error)
let line = a:error.fromy + 1
let prefix = a:error.contextoffset > 0 ? s:sanitize(a:error.context[: a:error.contextoffset-1]) : ''
let rest = a:error.context[a:error.contextoffset :]
let the_error = s:sanitize(rest[: a:error.errorlength-1])
let rest = s:sanitize(rest[a:error.errorlength :])
return '\V' . prefix . '\zs' . the_error . '\ze' . rest
endfunction
function! s:unescape_xml(str)
let s = substitute(a:str, '&quot;', '"', 'g')
let s = substitute(s, '&apos;', "'", 'g')
let s = substitute(s, '&gt;', '>', 'g')
let s = substitute(s, '&lt;', '<', 'g')
return substitute(s, '&amp;', '\&', 'g')
endfunction
function! s:unescape_error(err)
for e in ['context', 'msg', 'replacements']
let a:err[e] = s:unescape_xml(a:err[e])
endfor
return a:err
endfunction
function! grammarous#get_errors_from_xml(xml)
return map(filter(a:xml.childNodes(), 'v:val.name ==# "error"'), 's:unescape_error(v:val.attr)')
endfunction
function! s:matcherrpos(...)
return matchaddpos('GrammarousError', [a:000], 999)
endfunction
function! s:highlight_error(from, to)
if a:from[0] == a:to[0]
return s:matcherrpos(a:from[0], a:from[1], a:to[1] - a:from[1])
endif
let ids = [s:matcherrpos(a:from[0], a:from[1], strlen(getline(a:from[0]))+1 - a:from[1])]
let line = a:from[0] + 1
while line < a:to[0]
call add(ids, s:matcherrpos(line))
let line += 1
endwhile
call add(ids, s:matcherrpos(a:to[0], 1, a:to[1] - 1))
return ids
endfunction
function! s:remove_3dots(str)
return substitute(substitute(a:str, '\.\.\.$', '', ''), '\\V\zs\.\.\.', '', '')
endfunction
function! grammarous#highlight_errors_in_current_buffer(errs)
if !g:grammarous#use_fallback_highlight
for e in a:errs
let e.id = s:highlight_error(
\ [str2nr(e.fromy)+1, str2nr(e.fromx)+1],
\ [str2nr(e.toy)+1, str2nr(e.tox)+1],
\ )
endfor
else
for e in a:errs
let e.id = matchadd(
\ 'GrammarousError',
\ s:remove_3dots(grammarous#generate_highlight_pattern(e)),
\ 999
\ )
endfor
endif
endfunction
function! grammarous#reset_highlights()
for m in filter(getmatches(), 'v:val.group ==# "GrammarousError"')
call matchdelete(m.id)
endfor
endfunction
function! grammarous#find_checked_winnr() abort
if exists('b:grammarous_result')
return winnr()
endif
for bufnr in tabpagebuflist()
let result = getbufvar(bufnr, 'grammarous_result', [])
if empty(result)
continue
endif
let winnr = bufwinnr(bufnr)
if winnr == -1
continue
endif
return winnr
endfor
return -1
endfunction
function! grammarous#reset()
let win = grammarous#find_checked_winnr()
if win == -1
return
endif
let prev_win = winnr()
if win != prev_win
execute win . 'wincmd w'
endif
if g:grammarous#use_location_list
lclose
lgetexpr []
endif
call grammarous#reset_highlights()
call grammarous#info_win#stop_auto_preview()
call grammarous#info_win#close()
if exists('s:saved_spell')
let &l:spell = s:saved_spell
unlet s:saved_spell
endif
if has_key(g:grammarous#hooks, 'on_reset')
call call(g:grammarous#hooks.on_reset, [b:grammarous_result], g:grammarous#hooks)
endif
unlet! b:grammarous_result b:grammarous_preview_bufnr
if win != prev_win
wincmd p
endif
endfunction
let s:opt_parser = s:O.new()
\.on('--lang=VALUE', 'language to check', {'default' : g:grammarous#default_lang})
\.on('--[no-]preview', 'enable auto preview', {'default' : 1})
\.on('--[no-]comments-only', 'check comment only', {'default' : ''})
\.on('--[no-]move-to-first-error', 'move to first error', {'default' : g:grammarous#move_to_first_error})
\.on('--reinstall-languagetool', 'reinstall LanguageTool', {'default' : 0})
function! grammarous#complete_opt(arglead, cmdline, cursorpos)
return s:opt_parser.complete(a:arglead, a:cmdline, a:cursorpos)
endfunction
function! s:is_comment_only(option)
if type(a:option) == type(0)
return a:option
endif
return get(
\ g:grammarous#default_comments_only_filetypes,
\ &filetype,
\ get(g:grammarous#default_comments_only_filetypes, '*', 0)
\ )
endfunction
function! grammarous#check_current_buffer(qargs, range)
if exists('b:grammarous_result')
call grammarous#reset()
redraw!
endif
let parsed = s:opt_parser.parse(a:qargs, a:range, '')
if has_key(parsed, 'help')
return
endif
let b:grammarous_auto_preview = parsed.preview
if parsed.preview
call grammarous#info_win#start_auto_preview()
endif
if parsed['reinstall-languagetool']
call s:delete_jar_dir()
endif
" XXX
let s:last_parsed_options = parsed
call s:invoke_check(
\ parsed.__range__[0],
\ parsed.lang,
\ getline(parsed.__range__[0], parsed.__range__[1])
\ )
endfunction
function! s:less_position(p1, p2)
if a:p1[0] != a:p2[0]
return a:p1[0] < a:p2[0]
endif
return a:p1[1] < a:p2[1]
endfunction
function! s:binary_search_by_pos(errors, the_pos, start, end)
if a:start > a:end
return {}
endif
let m = (a:start + a:end) / 2
let from = [a:errors[m].fromy+1, a:errors[m].fromx+1]
let to = [a:errors[m].toy+1, a:errors[m].tox]
if s:less_position(a:the_pos, from)
return s:binary_search_by_pos(a:errors, a:the_pos, a:start, m-1)
endif
if s:less_position(to, a:the_pos)
return s:binary_search_by_pos(a:errors, a:the_pos, m+1, a:end)
endif
return a:errors[m]
endfunction
" Note:
" It believes all errors are sorted by its position
function! grammarous#get_error_at(pos, errs)
return s:binary_search_by_pos(a:errs, a:pos, 0, len(a:errs)-1)
endfunction
function! grammarous#fixit(err)
if empty(a:err)
\ || !grammarous#move_to_checked_buf(a:err.fromy+1, a:err.fromx+1)
\ || a:err.replacements ==# ''
call grammarous#error('Cannot fix this error automatically.')
return
endif
let sel_save = &l:selection
let &l:selection = 'inclusive'
let save_g_reg = getreg('g', 1)
let save_g_regtype = getregtype('g')
try
normal! v
call cursor(a:err.toy+1, a:err.tox)
noautocmd normal! "gy
let from = getreg('g')
let to = split(a:err.replacements, '#', 1)[0]
call setreg('g', to, 'v')
normal! gv"gp
call grammarous#remove_error(a:err, get(a:, 1, b:grammarous_result))
echomsg printf("Fixed: '%s' -> '%s'", from, to)
finally
call setreg('g', save_g_reg, save_g_regtype)
let &l:selection = sel_save
endtry
endfunction
function! grammarous#fixall(errs)
for e in a:errs
call grammarous#fixit(e)
endfor
endfunction
function! s:move_to_pos(pos)
let p = type(a:pos[0]) == type([]) ? a:pos[0] : a:pos
return cursor(a:pos[0], a:pos[1]) != -1
endfunction
function! s:move_to(buf, pos)
if a:buf != bufnr('%')
let winnr = bufwinnr(a:buf)
if winnr == -1
return 0
endif
execute winnr . 'wincmd w'
endif
return s:move_to_pos(a:pos)
endfunction
function! grammarous#move_to_checked_buf(...)
if exists('b:grammarous_result')
return s:move_to_pos(a:000)
endif
if exists('b:grammarous_preview_original_bufnr')
return s:move_to(b:grammarous_preview_original_bufnr, a:000)
endif
for b in tabpagebuflist()
if !empty(getbufvar(b, 'grammarous_result', []))
return s:move_to(b, a:000)
endif
endfor
return 0
endfunction
function! grammarous#create_update_info_window_of(errs)
let e = grammarous#get_error_at(getpos('.')[1 : 2], a:errs)
if empty(e)
return
endif
if exists('b:grammarous_preview_bufnr')
let winnr = bufwinnr(b:grammarous_preview_bufnr)
if winnr == -1
let bufnr = grammarous#info_win#open(e, bufnr('%'))
else
execute winnr . 'wincmd w'
let bufnr = grammarous#info_win#update(e)
endif
else
let bufnr = grammarous#info_win#open(e, bufnr('%'))
endif
wincmd p
let b:grammarous_preview_bufnr = bufnr
endfunction
function! grammarous#create_and_jump_to_info_window_of(errs)
call grammarous#create_update_info_window_of(a:errs)
wincmd p
endfunction
function! s:remove_error_highlight(e)
let ids = type(a:e.id) == type([]) ? a:e.id : [a:e.id]
for i in ids
silent! if matchdelete(i) == -1
return 0
endif
endfor
return 1
endfunction
function! grammarous#remove_error(e, errs)
if !s:remove_error_highlight(a:e)
return 0
endif
for i in range(len(a:errs))
if type(a:errs[i].id) == type(a:e.id) && a:errs[i].id == a:e.id
call grammarous#info_win#close()
unlet a:errs[i]
return 1
endif
endfor
return 0
endfunction
function! grammarous#remove_error_at(pos, errs)
let e = grammarous#get_error_at(a:pos, a:errs)
if empty(e)
return 0
endif
return grammarous#remove_error(e, a:errs)
endfunction
function! grammarous#disable_rule(rule, errs)
call grammarous#info_win#close()
" Note:
" reverse() is needed because of removing elements in list
for i in reverse(range(len(a:errs)))
let e = a:errs[i]
if e.ruleId ==# a:rule
if !s:remove_error_highlight(e)
return 0
endif
unlet a:errs[i]
endif
endfor
echomsg 'Disabled rule: ' . a:rule
return 1
endfunction
function! grammarous#disable_rule_at(pos, errs)
let e = grammarous#get_error_at(a:pos, a:errs)
if empty(e)
return 0
endif
return grammarous#disable_rule(e.ruleId, a:errs)
endfunction
function! grammarous#disable_category(category, errs)
call grammarous#info_win#close()
" Note:
" reverse() is needed because of removing elements in list
for i in reverse(range(len(a:errs)))
let e = a:errs[i]
if e.categoryid ==# a:category
if !s:remove_error_highlight(e)
return 0
endif
unlet a:errs[i]
endif
endfor
echomsg 'Disabled category: ' . a:category
return 1
endfunction
function! grammarous#disable_category_at(pos, errs)
let e = grammarous#get_error_at(a:pos, a:errs)
if empty(e)
return 0
endif
return grammarous#disable_category(e.categoryid, a:errs)
endfunction
function! grammarous#move_to_next_error(pos, errs)
for e in a:errs
let p = [e.fromy+1, e.fromx+1]
if s:less_position(a:pos, p)
return s:move_to_pos(p)
endif
endfor
call grammarous#error('No next error found.')
return 0
endfunction
function! grammarous#move_to_previous_error(pos, errs)
for e in reverse(copy(a:errs))
let p = [e.fromy+1, e.fromx+1]
if s:less_position(p, a:pos)
return s:move_to_pos(p)
endif
endfor
call grammarous#error('No previous error found.')
return 0
endfunction
let &cpo = s:save_cpo
unlet s:save_cpo

View File

@ -0,0 +1,44 @@
function! s:error(about, dir)
let msg = printf('Could not download jar file because %s. Please download zip from %s and extract it to %s.', a:about, g:grammarous#jar_url, a:dir)
call grammarous#error(msg)
endfunction
function! grammarous#downloader#download(jar_dir)
if !isdirectory(a:jar_dir)
call mkdir(a:jar_dir, 'p')
endif
let tmp_file = tempname() . '.zip'
if !executable('unzip')
call s:error("'unzip' is not found", a:jar_dir)
return 0
endif
if executable('axel')
let cmd = printf('axel -a -n 2 -o %s %s 2>&1', tmp_file, g:grammarous#jar_url)
elseif executable('wget')
let cmd = printf('wget -O %s %s 2>&1', tmp_file, g:grammarous#jar_url)
elseif executable('curl')
let cmd = printf('curl -L -o %s %s 2>&1', tmp_file, g:grammarous#jar_url)
else
call s:error("could not find 'axel', 'curl', or 'wget'", a:jar_dir)
return 0
endif
echomsg 'Downloading jar file from ' . g:grammarous#jar_url . '...'
let cmd = printf('%s && unzip %s -d %s', cmd, tmp_file, a:jar_dir)
let result = system(cmd)
if v:shell_error
call s:error(printf("'%s' failed: %s", cmd, result), a:jar_dir)
return 0
endif
echomsg 'Done!'
" Should error handling?
call delete(tmp_file)
return 1
endfunction

View File

@ -0,0 +1,232 @@
let s:save_cpo = &cpo
set cpo&vim
function! grammarous#info_win#action_return()
call grammarous#move_to_checked_buf(b:grammarous_preview_error.fromy+1, b:grammarous_preview_error.fromx+1)
endfunction
function! grammarous#info_win#action_fixit()
call grammarous#fixit(b:grammarous_preview_error)
endfunction
function! grammarous#info_win#action_remove_error()
let e = b:grammarous_preview_error
if !grammarous#move_to_checked_buf(
\ b:grammarous_preview_error.fromy+1,
\ b:grammarous_preview_error.fromx+1 )
return
endif
call grammarous#remove_error(e, b:grammarous_result)
endfunction
function! grammarous#info_win#action_disable_rule()
let e = b:grammarous_preview_error
if !grammarous#move_to_checked_buf(
\ b:grammarous_preview_error.fromy+1,
\ b:grammarous_preview_error.fromx+1 )
return
endif
call grammarous#disable_rule(e.ruleId, b:grammarous_result)
endfunction
function! grammarous#info_win#action_next_error()
if !grammarous#move_to_checked_buf(
\ b:grammarous_preview_error.fromy+1,
\ b:grammarous_preview_error.fromx+1 )
return
endif
if !grammarous#move_to_next_error(getpos('.')[1 : 2], b:grammarous_result)
wincmd p
endif
endfunction
function! grammarous#info_win#action_previous_error()
if !grammarous#move_to_checked_buf(
\ b:grammarous_preview_error.fromy+1,
\ b:grammarous_preview_error.fromx+1 )
return
endif
if !grammarous#move_to_previous_error(getpos('.')[1 : 2], b:grammarous_result)
wincmd p
endif
endfunction
function! grammarous#info_win#action_help()
echo join([
\ '| Mappings | Description |',
\ '| -------- |:---------------------------------------------- |',
\ '| q | Quit the info window |',
\ '| <CR> | Move to the location of the error |',
\ '| f | Fix the error automatically |',
\ '| r | Remove the error without fix |',
\ '| R | Disable the grammar rule in the checked buffer |',
\ '| n | Move to the next error |',
\ '| p | Move to the previous error |',
\ ], "\n")
endfunction
function! s:get_info_buffer(e)
let lines =
\ [
\ 'Error: ' . a:e.category,
\ ' ' . a:e.msg,
\ '',
\ ]
if a:e.replacements !=# ''
let lines +=
\ [
\ 'Corrections:',
\ ' ' . join(split(a:e.replacements, '#', 1), '; '),
\ '',
\ ]
endif
let lines +=
\ [
\ 'Context:',
\ ' ' . a:e.context,
\ '',
\ "Press '?' in this window to show help",
\ ]
return lines
endfunction
function! grammarous#info_win#action_quit()
let s:do_not_preview = 1
let preview_bufnr = bufnr('%')
quit!
" Consider the case where :quit! does not navigate to the buffer
" where :GrammarousCheck checked.
for bufnr in tabpagebuflist()
let b = getbufvar(bufnr, 'grammarous_preview_bufnr', -1)
if b != preview_bufnr
continue
endif
let winnr = bufwinnr(bufnr)
if winnr == -1
continue
endif
execute winnr . 'wincmd w'
unlet b:grammarous_preview_bufnr
return
endfor
" Reach here when the original buffer was already closed
endfunction
function! grammarous#info_win#update(e)
let b:grammarous_preview_error = a:e
silent normal! gg"_dG
silent %delete _
call setline(1, s:get_info_buffer(a:e))
execute 1
setlocal modified
return bufnr('%')
endfunction
function! grammarous#info_win#open(e, bufnr)
execute g:grammarous#info_win_direction g:grammarous#info_window_height . 'new' '[Grammarous]'
let b:grammarous_preview_original_bufnr = a:bufnr
let b:grammarous_preview_error = a:e
call setline(1, s:get_info_buffer(a:e))
execute 1
syntax match GrammarousInfoSection "\%(Context\|Correction\):"
syntax match GrammarousInfoError "Error:.*$"
syntax match GrammarousInfoHelp "^Press '?' in this window to show help$"
execute 'syntax match GrammarousError "' . escape(grammarous#generate_highlight_pattern(a:e), '"') . '"'
setlocal nonumber
setlocal bufhidden=hide
setlocal buftype=nofile
setlocal readonly
setlocal nolist
setlocal nobuflisted
setlocal noswapfile
setlocal nospell
setlocal nomodeline
setlocal nofoldenable
setlocal noreadonly
setlocal foldcolumn=0
setlocal nomodified
nnoremap <silent><buffer>q :<C-u>call grammarous#info_win#action_quit()<CR>
nnoremap <silent><buffer><CR> :<C-u>call grammarous#info_win#action_return()<CR>
nnoremap <buffer>f :<C-u>call grammarous#info_win#action_fixit()<CR>
nnoremap <silent><buffer>r :<C-u>call grammarous#info_win#action_remove_error()<CR>
nnoremap <silent><buffer>R :<C-u>call grammarous#info_win#action_disable_rule()<CR>
nnoremap <silent><buffer>n :<C-u>call grammarous#info_win#action_next_error()<CR>
nnoremap <silent><buffer>p :<C-u>call grammarous#info_win#action_previous_error()<CR>
nnoremap <silent><buffer>? :<C-u>call grammarous#info_win#action_help()<CR>
return bufnr('%')
endfunction
function! s:lookup_preview_bufnr()
for b in tabpagebuflist()
let the_buf = getbufvar(b, 'grammarous_preview_bufnr', -1)
if the_buf != -1
return the_buf
endif
endfor
return -1
endfunction
function! grammarous#info_win#close()
let cur_win = winnr()
if exists('b:grammarous_preview_bufnr')
let prev_win = bufwinnr(b:grammarous_preview_bufnr)
else
let the_buf = s:lookup_preview_bufnr()
if the_buf == -1
return 0
endif
let prev_win = bufwinnr(the_buf)
endif
if prev_win == -1
return 0
end
execute prev_win . 'wincmd w'
wincmd c
execute cur_win . 'wincmd w'
return 1
endfunction
function! s:do_auto_preview()
let mode = mode()
if mode ==? 'v' || mode ==# "\<C-v>"
return
endif
if exists('s:do_not_preview')
unlet s:do_not_preview
return
endif
if !exists('b:grammarous_result') || empty(b:grammarous_result)
autocmd! plugin-grammarous-auto-preview
return
endif
call grammarous#create_update_info_window_of(b:grammarous_result)
endfunction
function! grammarous#info_win#start_auto_preview()
augroup plugin-grammarous-auto-preview
autocmd!
autocmd CursorMoved <buffer> call <SID>do_auto_preview()
augroup END
endfunction
function! grammarous#info_win#stop_auto_preview()
silent! autocmd! plugin-grammarous-auto-preview
endfunction
let &cpo = s:save_cpo
unlet s:save_cpo

View File

@ -0,0 +1,11 @@
function! s:is_empty_region(begin, end)
return a:begin[1] > a:end[1] || (a:begin[1] == a:end[1] && a:end[2] < a:begin[2])
endfunction
function! operator#grammarous#do(visual_kind)
if s:is_empty_region(getpos("'["), getpos("']"))
return
endif
call grammarous#check_current_buffer('', [getpos("'[")[1], getpos("']")[1]])
endfunction

View File

@ -0,0 +1,120 @@
let s:save_cpo = &cpo
set cpo&vim
let g:unite#sources#grammarous#one_line = get(g:, 'unite#sources#grammarous#one_line', 0)
let s:source = {
\ 'name' : 'grammarous',
\ 'description' : 'Show result of grammar check by vim-grammarous',
\ 'default_kind' : 'jump_list',
\ 'default_action' : 'open',
\ 'hooks' : {},
\ 'action_table' : {},
\ 'syntax' : 'uniteSource__Grammarous',
\ }
function! unite#sources#grammarous#define()
return s:source
endfunction
function! s:source.hooks.on_init(args, context)
if exists('b:unite') && has_key(b:unite, 'prev_bufnr')
let a:context.source__checked_bufnr = b:unite.prev_bufnr
else
let a:context.source__checked_bufnr = bufnr('%')
endif
let a:context.source__checked_bufnr
\ = getbufvar(
\ a:context.source__checked_bufnr,
\ 'grammarous_preview_original_bufnr',
\ a:context.source__checked_bufnr
\ )
if type(getbufvar(a:context.source__checked_bufnr, 'grammarous_result', 0)) == type(0)
let should_check_current_buf = a:context.source__checked_bufnr == bufnr('%')
if should_check_current_buf
execute 'GrammarousCheck' join(a:args, ' ')
else
let w = bufwinnr(a:context.source__checked_bufnr)
execute w . 'wincmd w'
execute 'GrammarousCheck' join(a:args, ' ')
wincmd p
endif
endif
call grammarous#info_win#close()
endfunction
function! s:source.hooks.on_syntax(args, context)
if g:unite#sources#grammarous#one_line
syntax region uniteSource__GrammarousError start="'" end="'" oneline contained containedin=uniteSource__Grammarous
syntax match uniteSource__GrammarousArrow "->" contained containedin=uniteSource__Grammarous
highlight default link uniteSource__GrammarousArrow Keyword
highlight default link uniteSource__GrammarousError ErrorMsg
else
syntax match uniteSource__GrammarousKeyword "\%(Context\|Correct\):" contained containedin=uniteSource__Grammarous
syntax keyword uniteSource__GrammarousError Error contained containedin=uniteSource__Grammarous
highlight default link uniteSource__GrammarousKeyword Keyword
highlight default link uniteSource__GrammarousError ErrorMsg
for err in getbufvar(a:context.source__checked_bufnr, 'grammarous_result', [])
call matchadd('GrammarousError', grammarous#generate_highlight_pattern(err), 999)
endfor
endif
endfunction
function! s:make_word(e)
if g:unite#sources#grammarous#one_line
return printf("'%s' -> %s", a:e.context[a:e.contextoffset : a:e.contextoffset+a:e.errorlength-1], a:e.msg)
else
let word = printf('Error: %s\nContext: %s', a:e.msg, a:e.context)
if a:e.replacements !=# ''
let word .= '\nCorrect: ' . split(a:e.replacements, '#', 1)[0]
endif
return word
endif
endfunction
function! s:source.change_candidates(args, context)
return map(copy(getbufvar(a:context.source__checked_bufnr, 'grammarous_result', [])), '{
\ "word" : s:make_word(v:val),
\ "action__buffer_nr" : a:context.source__checked_bufnr,
\ "action__line" : str2nr(v:val.fromy)+1,
\ "action__col" : str2nr(v:val.fromx)+1,
\ "action__grammar_error" : v:val,
\ "is_multiline" : 1,
\}')
endfunction
function! s:prepare_bufvar(c)
let b:grammarous_preview_error = a:c.action__grammar_error
let b:grammarous_preview_original_bufnr = a:c.action__buffer_nr
endfunction
let s:source.action_table.fixit = {
\ 'description' : 'Fix the error automatically',
\ }
function! s:source.action_table.fixit.func(candidate)
call s:prepare_bufvar(a:candidate)
call grammarous#info_win#action_fixit()
endfunction
let s:source.action_table.remove_error = {
\ 'description' : 'Remove the error without fix'
\ }
function! s:source.action_table.remove_error.func(candidate)
call s:prepare_bufvar(a:candidate)
call grammarous#info_win#action_remove_error()
endfunction
let s:source.action_table.disable_rule = {
\ 'description' : 'Disable the grammar rule in the checked buffer'
\ }
function! s:source.action_table.disable_rule.func(candidate)
call s:prepare_bufvar(a:candidate)
call grammarous#info_win#action_disable_rule()
endfunction
let &cpo = s:save_cpo
unlet s:save_cpo

View File

@ -0,0 +1,12 @@
function! vital#of(name) abort
let files = globpath(&runtimepath, 'autoload/vital/' . a:name . '.vital', 1)
let file = split(files, "\n")
if empty(file)
throw 'vital: version file not found: ' . a:name
endif
let ver = readfile(file[0], 'b')
if empty(ver)
throw 'vital: invalid version file: ' . a:name
endif
return vital#_{substitute(ver[0], '\W', '', 'g')}#new()
endfunction

View File

@ -0,0 +1,5 @@
let s:_plugin_name = expand('<sfile>:t:r')
function! vital#{s:_plugin_name}#new() abort
return vital#{s:_plugin_name[1:]}#new()
endfunction

View File

@ -0,0 +1,462 @@
" ___vital___
" NOTE: lines between '" ___vital___' is generated by :Vitalize.
" Do not mofidify the code nor insert new lines before '" ___vital___'
if v:version > 703 || v:version == 703 && has('patch1170')
function! vital#_grammarous#Data#List#import() abort
return map({'combinations': '', 'and': '', 'sort_by': '', 'foldr1': '', 'sort': '', 'flatten': '', 'has_index': '', 'find_indices': '', 'any': '', 'unshift': '', 'span': '', 'pop': '', 'binary_search': '', 'uniq_by': '', 'or': '', 'all': '', 'zip': '', 'find_last_index': '', 'find': '', 'partition': '', 'shift': '', 'permutations': '', 'break': '', 'max_by': '', 'foldl': '', 'foldr': '', 'find_index': '', 'drop_while': '', 'group_by': '', 'take_while': '', 'conj': '', 'push': '', 'char_range': '', 'cons': '', 'foldl1': '', 'intersect': '', 'concat': '', 'map_accum': '', 'clear': '', 'has_common_items': '', 'product': '', 'zip_fill': '', 'uniq': '', 'has': '', 'min_by': '', 'with_index': ''}, 'function("s:" . v:key)')
endfunction
else
function! s:_SID() abort
return matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze__SID$')
endfunction
execute join(['function! vital#_grammarous#Data#List#import() abort', printf("return map({'combinations': '', 'and': '', 'sort_by': '', 'foldr1': '', 'sort': '', 'flatten': '', 'has_index': '', 'find_indices': '', 'any': '', 'unshift': '', 'span': '', 'pop': '', 'binary_search': '', 'uniq_by': '', 'or': '', 'all': '', 'zip': '', 'find_last_index': '', 'find': '', 'partition': '', 'shift': '', 'permutations': '', 'break': '', 'max_by': '', 'foldl': '', 'foldr': '', 'find_index': '', 'drop_while': '', 'group_by': '', 'take_while': '', 'conj': '', 'push': '', 'char_range': '', 'cons': '', 'foldl1': '', 'intersect': '', 'concat': '', 'map_accum': '', 'clear': '', 'has_common_items': '', 'product': '', 'zip_fill': '', 'uniq': '', 'has': '', 'min_by': '', 'with_index': ''}, \"function('<SNR>%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n")
delfunction s:_SID
endif
" ___vital___
" Utilities for list.
let s:save_cpo = &cpo
set cpo&vim
function! s:pop(list) abort
return remove(a:list, -1)
endfunction
function! s:push(list, val) abort
call add(a:list, a:val)
return a:list
endfunction
function! s:shift(list) abort
return remove(a:list, 0)
endfunction
function! s:unshift(list, val) abort
return insert(a:list, a:val)
endfunction
function! s:cons(x, xs) abort
return [a:x] + a:xs
endfunction
function! s:conj(xs, x) abort
return a:xs + [a:x]
endfunction
" Removes duplicates from a list.
function! s:uniq(list) abort
return s:uniq_by(a:list, 'v:val')
endfunction
" Removes duplicates from a list.
function! s:uniq_by(list, f) abort
let list = map(copy(a:list), printf('[v:val, %s]', a:f))
let i = 0
let seen = {}
while i < len(list)
let key = string(list[i][1])
if has_key(seen, key)
call remove(list, i)
else
let seen[key] = 1
let i += 1
endif
endwhile
return map(list, 'v:val[0]')
endfunction
function! s:clear(list) abort
if !empty(a:list)
unlet! a:list[0 : len(a:list) - 1]
endif
return a:list
endfunction
" Concatenates a list of lists.
" XXX: Should we verify the input?
function! s:concat(list) abort
let memo = []
for Value in a:list
let memo += Value
endfor
return memo
endfunction
" Take each elements from lists to a new list.
function! s:flatten(list, ...) abort
let limit = a:0 > 0 ? a:1 : -1
let memo = []
if limit == 0
return a:list
endif
let limit -= 1
for Value in a:list
let memo +=
\ type(Value) == type([]) ?
\ s:flatten(Value, limit) :
\ [Value]
unlet! Value
endfor
return memo
endfunction
" Sorts a list with expression to compare each two values.
" a:a and a:b can be used in {expr}.
function! s:sort(list, expr) abort
if type(a:expr) == type(function('function'))
return sort(a:list, a:expr)
endif
let s:expr = a:expr
return sort(a:list, 's:_compare')
endfunction
function! s:_compare(a, b) abort
return eval(s:expr)
endfunction
" Sorts a list using a set of keys generated by mapping the values in the list
" through the given expr.
" v:val is used in {expr}
function! s:sort_by(list, expr) abort
let pairs = map(a:list, printf('[v:val, %s]', a:expr))
return map(s:sort(pairs,
\ 'a:a[1] ==# a:b[1] ? 0 : a:a[1] ># a:b[1] ? 1 : -1'), 'v:val[0]')
endfunction
" Returns a maximum value in {list} through given {expr}.
" Returns 0 if {list} is empty.
" v:val is used in {expr}
function! s:max_by(list, expr) abort
if empty(a:list)
return 0
endif
let list = map(copy(a:list), a:expr)
return a:list[index(list, max(list))]
endfunction
" Returns a minimum value in {list} through given {expr}.
" Returns 0 if {list} is empty.
" v:val is used in {expr}
" FIXME: -0x80000000 == 0x80000000
function! s:min_by(list, expr) abort
return s:max_by(a:list, '-(' . a:expr . ')')
endfunction
" Returns List of character sequence between [a:from, a:to]
" e.g.: s:char_range('a', 'c') returns ['a', 'b', 'c']
function! s:char_range(from, to) abort
return map(
\ range(char2nr(a:from), char2nr(a:to)),
\ 'nr2char(v:val)'
\)
endfunction
" Returns true if a:list has a:value.
" Returns false otherwise.
function! s:has(list, value) abort
return index(a:list, a:value) isnot -1
endfunction
" Returns true if a:list[a:index] exists.
" Returns false otherwise.
" NOTE: Returns false when a:index is negative number.
function! s:has_index(list, index) abort
" Return true when negative index?
" let index = a:index >= 0 ? a:index : len(a:list) + a:index
return 0 <= a:index && a:index < len(a:list)
endfunction
" similar to Haskell's Data.List.span
function! s:span(f, xs) abort
let border = len(a:xs)
for i in range(len(a:xs))
if !eval(substitute(a:f, 'v:val', string(a:xs[i]), 'g'))
let border = i
break
endif
endfor
return border == 0 ? [[], copy(a:xs)] : [a:xs[: border - 1], a:xs[border :]]
endfunction
" similar to Haskell's Data.List.break
function! s:break(f, xs) abort
return s:span(printf('!(%s)', a:f), a:xs)
endfunction
" similar to Haskell's Data.List.takeWhile
function! s:take_while(f, xs) abort
return s:span(a:f, a:xs)[0]
endfunction
" similar to Haskell's Data.List.dropWhile
function! s:drop_while(f, xs) abort
return s:span(a:f, a:xs)[1]
endfunction
" similar to Haskell's Data.List.partition
function! s:partition(f, xs) abort
return [filter(copy(a:xs), a:f), filter(copy(a:xs), '!(' . a:f . ')')]
endfunction
" similar to Haskell's Prelude.all
function! s:all(f, xs) abort
return !s:any(printf('!(%s)', a:f), a:xs)
endfunction
" similar to Haskell's Prelude.any
function! s:any(f, xs) abort
return !empty(filter(map(copy(a:xs), a:f), 'v:val'))
endfunction
" similar to Haskell's Prelude.and
function! s:and(xs) abort
return s:all('v:val', a:xs)
endfunction
" similar to Haskell's Prelude.or
function! s:or(xs) abort
return s:any('v:val', a:xs)
endfunction
function! s:map_accum(expr, xs, init) abort
let memo = []
let init = a:init
for x in a:xs
let expr = substitute(a:expr, 'v:memo', init, 'g')
let expr = substitute(expr, 'v:val', x, 'g')
let [tmp, init] = eval(expr)
call add(memo, tmp)
endfor
return memo
endfunction
" similar to Haskell's Prelude.foldl
function! s:foldl(f, init, xs) abort
let memo = a:init
for x in a:xs
let expr = substitute(a:f, 'v:val', string(x), 'g')
let expr = substitute(expr, 'v:memo', string(memo), 'g')
unlet memo
let memo = eval(expr)
endfor
return memo
endfunction
" similar to Haskell's Prelude.foldl1
function! s:foldl1(f, xs) abort
if len(a:xs) == 0
throw 'vital: Data.List: foldl1'
endif
return s:foldl(a:f, a:xs[0], a:xs[1:])
endfunction
" similar to Haskell's Prelude.foldr
function! s:foldr(f, init, xs) abort
return s:foldl(a:f, a:init, reverse(copy(a:xs)))
endfunction
" similar to Haskell's Prelude.fold11
function! s:foldr1(f, xs) abort
if len(a:xs) == 0
throw 'vital: Data.List: foldr1'
endif
return s:foldr(a:f, a:xs[-1], a:xs[0:-2])
endfunction
" similar to python's zip()
function! s:zip(...) abort
return map(range(min(map(copy(a:000), 'len(v:val)'))), "map(copy(a:000), 'v:val['.v:val.']')")
endfunction
" similar to zip(), but goes until the longer one.
function! s:zip_fill(xs, ys, filler) abort
if empty(a:xs) && empty(a:ys)
return []
elseif empty(a:ys)
return s:cons([a:xs[0], a:filler], s:zip_fill(a:xs[1 :], [], a:filler))
elseif empty(a:xs)
return s:cons([a:filler, a:ys[0]], s:zip_fill([], a:ys[1 :], a:filler))
else
return s:cons([a:xs[0], a:ys[0]], s:zip_fill(a:xs[1 :], a:ys[1: ], a:filler))
endif
endfunction
" Inspired by Ruby's with_index method.
function! s:with_index(list, ...) abort
let base = a:0 > 0 ? a:1 : 0
return map(copy(a:list), '[v:val, v:key + base]')
endfunction
" similar to Ruby's detect or Haskell's find.
function! s:find(list, default, f) abort
for x in a:list
if eval(substitute(a:f, 'v:val', string(x), 'g'))
return x
endif
endfor
return a:default
endfunction
" Returns the index of the first element which satisfies the given expr.
function! s:find_index(xs, f, ...) abort
let len = len(a:xs)
let start = a:0 > 0 ? (a:1 < 0 ? len + a:1 : a:1) : 0
let default = a:0 > 1 ? a:2 : -1
if start >=# len || start < 0
return default
endif
for i in range(start, len - 1)
if eval(substitute(a:f, 'v:val', string(a:xs[i]), 'g'))
return i
endif
endfor
return default
endfunction
" Returns the index of the last element which satisfies the given expr.
function! s:find_last_index(xs, f, ...) abort
let len = len(a:xs)
let start = a:0 > 0 ? (a:1 < 0 ? len + a:1 : a:1) : len - 1
let default = a:0 > 1 ? a:2 : -1
if start >=# len || start < 0
return default
endif
for i in range(start, 0, -1)
if eval(substitute(a:f, 'v:val', string(a:xs[i]), 'g'))
return i
endif
endfor
return default
endfunction
" Similar to find_index but returns the list of indices satisfying the given expr.
function! s:find_indices(xs, f, ...) abort
let len = len(a:xs)
let start = a:0 > 0 ? (a:1 < 0 ? len + a:1 : a:1) : 0
let result = []
if start >=# len || start < 0
return result
endif
for i in range(start, len - 1)
if eval(substitute(a:f, 'v:val', string(a:xs[i]), 'g'))
call add(result, i)
endif
endfor
return result
endfunction
" Return non-zero if a:list1 and a:list2 have any common item(s).
" Return zero otherwise.
function! s:has_common_items(list1, list2) abort
return !empty(filter(copy(a:list1), 'index(a:list2, v:val) isnot -1'))
endfunction
function! s:intersect(list1, list2) abort
let items = []
" for funcref
for X in a:list1
if index(a:list2, X) != -1 && index(items, X) == -1
let items += [X]
endif
endfor
return items
endfunction
" similar to Ruby's group_by.
function! s:group_by(xs, f) abort
let result = {}
let list = map(copy(a:xs), printf('[v:val, %s]', a:f))
for x in list
let Val = x[0]
let key = type(x[1]) !=# type('') ? string(x[1]) : x[1]
if has_key(result, key)
call add(result[key], Val)
else
let result[key] = [Val]
endif
unlet Val
endfor
return result
endfunction
function! s:_default_compare(a, b) abort
return a:a <# a:b ? -1 : a:a ># a:b ? 1 : 0
endfunction
function! s:binary_search(list, value, ...) abort
let Predicate = a:0 >= 1 ? a:1 : 's:_default_compare'
let dic = a:0 >= 2 ? a:2 : {}
let start = 0
let end = len(a:list) - 1
while 1
if start > end
return -1
endif
let middle = (start + end) / 2
let compared = call(Predicate, [a:value, a:list[middle]], dic)
if compared < 0
let end = middle - 1
elseif compared > 0
let start = middle + 1
else
return middle
endif
endwhile
endfunction
function! s:product(lists) abort
let result = [[]]
for pool in a:lists
let tmp = []
for x in result
let tmp += map(copy(pool), 'x + [v:val]')
endfor
let result = tmp
endfor
return result
endfunction
function! s:permutations(list, ...) abort
if a:0 > 1
throw 'vital: Data.List: too many arguments'
endif
let r = a:0 == 1 ? a:1 : len(a:list)
if r > len(a:list)
return []
elseif r < 0
throw 'vital: Data.List: {r} must be non-negative integer'
endif
let n = len(a:list)
let result = []
for indices in s:product(map(range(r), 'range(n)'))
if len(s:uniq(indices)) == r
call add(result, map(indices, 'a:list[v:val]'))
endif
endfor
return result
endfunction
function! s:combinations(list, r) abort
if a:r > len(a:list)
return []
elseif a:r < 0
throw 'vital: Data.List: {r} must be non-negative integer'
endif
let n = len(a:list)
let result = []
for indices in s:permutations(range(n), a:r)
if s:sort(copy(indices), 'a:a - a:b') == indices
call add(result, map(indices, 'a:list[v:val]'))
endif
endfor
return result
endfunction
let &cpo = s:save_cpo
unlet s:save_cpo
" vim:set et ts=2 sts=2 sw=2 tw=0:

View File

@ -0,0 +1,633 @@
" ___vital___
" NOTE: lines between '" ___vital___' is generated by :Vitalize.
" Do not mofidify the code nor insert new lines before '" ___vital___'
if v:version > 703 || v:version == 703 && has('patch1170')
function! vital#_grammarous#Data#String#import() abort
return map({'starts_with': '', 'split3': '', 'replace_first': '', 'chop': '', 'unescape': '', 'split_posix_text': '', 'replace': '', 'scan': '', 'strwidthpart': '', 'common_head': '', 'reverse': '', 'escape_pattern': '', 'trim_end': '', '_vital_depends': '', 'wrap': '', 'join_posix_lines': '', 'contains_multibyte': '', 'truncate_skipping': '', 'split_leftright': '', 'ends_with': '', 'nsplit': '', 'strwidthpart_reverse': '', 'unescape_pattern': '', 'levenshtein_distance': '', 'trim_start': '', 'justify_equal_spacing': '', 'nr2hex': '', 'iconv': '', 'pad_left': '', 'nr2enc_char': '', 'lines': '', 'repair_posix_text': '', 'nr2byte': '', 'trim': '', 'diffidx': '', 'truncate': '', 'split_by_displaywidth': '', '_vital_created': '', 'padding_by_displaywidth': '', 'hash': '', 'chomp': '', 'pad_between_letters': '', 'dstring': '', 'pad_both_sides': '', 'substitute_last': '', 'pad_right': '', 'remove_ansi_sequences': '', '_vital_loaded': ''}, 'function("s:" . v:key)')
endfunction
else
function! s:_SID() abort
return matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze__SID$')
endfunction
execute join(['function! vital#_grammarous#Data#String#import() abort', printf("return map({'starts_with': '', 'split3': '', 'replace_first': '', 'chop': '', 'unescape': '', 'split_posix_text': '', 'replace': '', 'scan': '', 'strwidthpart': '', 'common_head': '', 'reverse': '', 'escape_pattern': '', 'trim_end': '', '_vital_depends': '', 'wrap': '', 'join_posix_lines': '', 'contains_multibyte': '', 'truncate_skipping': '', 'split_leftright': '', 'ends_with': '', 'nsplit': '', 'strwidthpart_reverse': '', 'unescape_pattern': '', 'levenshtein_distance': '', 'trim_start': '', 'justify_equal_spacing': '', 'nr2hex': '', 'iconv': '', 'pad_left': '', 'nr2enc_char': '', 'lines': '', 'repair_posix_text': '', 'nr2byte': '', 'trim': '', 'diffidx': '', 'truncate': '', 'split_by_displaywidth': '', '_vital_created': '', 'padding_by_displaywidth': '', 'hash': '', 'chomp': '', 'pad_between_letters': '', 'dstring': '', 'pad_both_sides': '', 'substitute_last': '', 'pad_right': '', 'remove_ansi_sequences': '', '_vital_loaded': ''}, \"function('<SNR>%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n")
delfunction s:_SID
endif
" ___vital___
" Utilities for string.
let s:save_cpo = &cpo
set cpo&vim
function! s:_vital_loaded(V) abort
let s:V = a:V
let s:L = s:V.import('Data.List')
endfunction
function! s:_vital_depends() abort
return ['Data.List']
endfunction
function! s:_vital_created(module) abort
" Expose script-local funcref
if exists('s:strchars')
let a:module.strchars = s:strchars
endif
if exists('s:wcswidth')
let a:module.wcswidth = s:wcswidth
endif
endfunction
" Substitute a:from => a:to by string.
" To substitute by pattern, use substitute() instead.
function! s:replace(str, from, to) abort
return s:_replace(a:str, a:from, a:to, 'g')
endfunction
" Substitute a:from => a:to only once.
" cf. s:replace()
function! s:replace_first(str, from, to) abort
return s:_replace(a:str, a:from, a:to, '')
endfunction
" implement of replace() and replace_first()
function! s:_replace(str, from, to, flags) abort
return substitute(a:str, '\V'.escape(a:from, '\'), escape(a:to, '\'), a:flags)
endfunction
function! s:scan(str, pattern) abort
let list = []
call substitute(a:str, a:pattern, '\=add(list, submatch(0)) == [] ? "" : ""', 'g')
return list
endfunction
function! s:reverse(str) abort
return join(reverse(split(a:str, '.\zs')), '')
endfunction
function! s:starts_with(str, prefix) abort
return stridx(a:str, a:prefix) == 0
endfunction
function! s:ends_with(str, suffix) abort
let idx = strridx(a:str, a:suffix)
return 0 <= idx && idx + len(a:suffix) == len(a:str)
endfunction
function! s:common_head(strs) abort
if empty(a:strs)
return ''
endif
let len = len(a:strs)
if len == 1
return a:strs[0]
endif
let strs = len == 2 ? a:strs : sort(copy(a:strs))
let pat = substitute(strs[0], '.', '\="[" . escape(submatch(0), "^\\") . "]"', 'g')
return pat ==# '' ? '' : matchstr(strs[-1], '\C^\%[' . pat . ']')
endfunction
" Split to two elements of List. ([left, right])
" e.g.: s:split3('neocomplcache', 'compl') returns ['neo', 'compl', 'cache']
function! s:split_leftright(expr, pattern) abort
let [left, _, right] = s:split3(a:expr, a:pattern)
return [left, right]
endfunction
function! s:split3(expr, pattern) abort
let ERROR = ['', '', '']
if a:expr ==# '' || a:pattern ==# ''
return ERROR
endif
let begin = match(a:expr, a:pattern)
if begin is -1
return ERROR
endif
let end = matchend(a:expr, a:pattern)
let left = begin <=# 0 ? '' : a:expr[: begin - 1]
let right = a:expr[end :]
return [left, a:expr[begin : end-1], right]
endfunction
" Slices into strings determines the number of substrings.
" e.g.: s:nsplit("neo compl cache", 2, '\s') returns ['neo', 'compl cache']
function! s:nsplit(expr, n, ...) abort
let pattern = get(a:000, 0, '\s')
let keepempty = get(a:000, 1, 1)
let ret = []
let expr = a:expr
if a:n <= 1
return [expr]
endif
while 1
let pos = match(expr, pattern)
if pos == -1
if expr !~ pattern || keepempty
call add(ret, expr)
endif
break
elseif pos >= 0
let left = pos > 0 ? expr[:pos-1] : ''
if pos > 0 || keepempty
call add(ret, left)
endif
let ml = len(matchstr(expr, pattern))
if pos == 0 && ml == 0
let pos = 1
endif
let expr = expr[pos+ml :]
endif
if len(expr) == 0
break
endif
if len(ret) == a:n - 1
call add(ret, expr)
break
endif
endwhile
return ret
endfunction
" Returns the number of character in a:str.
" NOTE: This returns proper value
" even if a:str contains multibyte character(s).
" s:strchars(str) {{{
if exists('*strchars')
let s:strchars = function('strchars')
else
function! s:strchars(str) abort
return strlen(substitute(copy(a:str), '.', 'x', 'g'))
endfunction
endif "}}}
" Returns the bool of contains any multibyte character in s:str
function! s:contains_multibyte(str) abort "{{{
return strlen(a:str) != s:strchars(a:str)
endfunction "}}}
" Remove last character from a:str.
" NOTE: This returns proper value
" even if a:str contains multibyte character(s).
function! s:chop(str) abort "{{{
return substitute(a:str, '.$', '', '')
endfunction "}}}
" Remove last \r,\n,\r\n from a:str.
function! s:chomp(str) abort "{{{
return substitute(a:str, '\%(\r\n\|[\r\n]\)$', '', '')
endfunction "}}}
" wrap() and its internal functions
" * _split_by_wcswidth_once()
" * _split_by_wcswidth()
" * _concat()
" * wrap()
"
" NOTE _concat() is just a copy of Data.List.concat().
" FIXME don't repeat yourself
function! s:_split_by_wcswidth_once(body, x) abort
let fst = s:strwidthpart(a:body, a:x)
let snd = s:strwidthpart_reverse(a:body, s:wcswidth(a:body) - s:wcswidth(fst))
return [fst, snd]
endfunction
function! s:_split_by_wcswidth(body, x) abort
let memo = []
let body = a:body
while s:wcswidth(body) > a:x
let [tmp, body] = s:_split_by_wcswidth_once(body, a:x)
call add(memo, tmp)
endwhile
call add(memo, body)
return memo
endfunction
function! s:trim(str) abort
return matchstr(a:str,'^\s*\zs.\{-}\ze\s*$')
endfunction
function! s:trim_start(str) abort
return matchstr(a:str,'^\s*\zs.\{-}$')
endfunction
function! s:trim_end(str) abort
return matchstr(a:str,'^.\{-}\ze\s*$')
endfunction
function! s:wrap(str,...) abort
let _columns = a:0 > 0 ? a:1 : &columns
return s:L.concat(
\ map(split(a:str, '\r\n\|[\r\n]'), 's:_split_by_wcswidth(v:val, _columns - 1)'))
endfunction
function! s:nr2byte(nr) abort
if a:nr < 0x80
return nr2char(a:nr)
elseif a:nr < 0x800
return nr2char(a:nr/64+192).nr2char(a:nr%64+128)
else
return nr2char(a:nr/4096%16+224).nr2char(a:nr/64%64+128).nr2char(a:nr%64+128)
endif
endfunction
function! s:nr2enc_char(charcode) abort
if &encoding ==# 'utf-8'
return nr2char(a:charcode)
endif
let char = s:nr2byte(a:charcode)
if strlen(char) > 1
let char = strtrans(iconv(char, 'utf-8', &encoding))
endif
return char
endfunction
function! s:nr2hex(nr) abort
let n = a:nr
let r = ''
while n
let r = '0123456789ABCDEF'[n % 16] . r
let n = n / 16
endwhile
return r
endfunction
" If a ==# b, returns -1.
" If a !=# b, returns first index of different character.
function! s:diffidx(a, b) abort
return a:a ==# a:b ? -1 : strlen(s:common_head([a:a, a:b]))
endfunction
function! s:substitute_last(expr, pat, sub) abort
return substitute(a:expr, printf('.*\zs%s', a:pat), a:sub, '')
endfunction
function! s:dstring(expr) abort
let x = substitute(string(a:expr), "^'\\|'$", '', 'g')
let x = substitute(x, "''", "'", 'g')
return printf('"%s"', escape(x, '"'))
endfunction
function! s:lines(str) abort
return split(a:str, '\r\?\n')
endfunction
function! s:_pad_with_char(str, left, right, char) abort
return repeat(a:char, a:left). a:str. repeat(a:char, a:right)
endfunction
function! s:pad_left(str, width, ...) abort
let char = get(a:, 1, ' ')
if strdisplaywidth(char) != 1
throw "vital: Data.String: Can't use non-half-width characters for padding."
endif
let left = max([0, a:width - strdisplaywidth(a:str)])
return s:_pad_with_char(a:str, left, 0, char)
endfunction
function! s:pad_right(str, width, ...) abort
let char = get(a:, 1, ' ')
if strdisplaywidth(char) != 1
throw "vital: Data.String: Can't use non-half-width characters for padding."
endif
let right = max([0, a:width - strdisplaywidth(a:str)])
return s:_pad_with_char(a:str, 0, right, char)
endfunction
function! s:pad_both_sides(str, width, ...) abort
let char = get(a:, 1, ' ')
if strdisplaywidth(char) != 1
throw "vital: Data.String: Can't use non-half-width characters for padding."
endif
let space = max([0, a:width - strdisplaywidth(a:str)])
let left = space / 2
let right = space - left
return s:_pad_with_char(a:str, left, right, char)
endfunction
function! s:pad_between_letters(str, width, ...) abort
let char = get(a:, 1, ' ')
if strdisplaywidth(char) != 1
throw "vital: Data.String: Can't use non-half-width characters for padding."
endif
let letters = split(a:str, '\zs')
let each_width = a:width / len(letters)
let str = join(map(letters, 's:pad_both_sides(v:val, each_width, char)'), '')
if a:width - strdisplaywidth(str) > 0
return char. s:pad_both_sides(str, a:width - 1, char)
endif
return str
endfunction
function! s:justify_equal_spacing(str, width, ...) abort
let char = get(a:, 1, ' ')
if strdisplaywidth(char) != 1
throw "vital: Data.String: Can't use non-half-width characters for padding."
endif
let letters = split(a:str, '\zs')
let first_letter = letters[0]
" {width w/o the first letter} / {length w/o the first letter}
let each_width = (a:width - strdisplaywidth(first_letter)) / (len(letters) - 1)
let remainder = (a:width - strdisplaywidth(first_letter)) % (len(letters) - 1)
return first_letter. join(s:L.concat([
\ map(letters[1:remainder], 's:pad_left(v:val, each_width + 1, char)'),
\ map(letters[remainder + 1:], 's:pad_left(v:val, each_width, char)')
\ ]), '')
endfunction
function! s:levenshtein_distance(str1, str2) abort
let letters1 = split(a:str1, '\zs')
let letters2 = split(a:str2, '\zs')
let length1 = len(letters1)
let length2 = len(letters2)
let distances = map(range(1, length1 + 1), 'map(range(1, length2 + 1), ''0'')')
for i1 in range(0, length1)
let distances[i1][0] = i1
endfor
for i2 in range(0, length2)
let distances[0][i2] = i2
endfor
for i1 in range(1, length1)
for i2 in range(1, length2)
let cost = (letters1[i1 - 1] ==# letters2[i2 - 1]) ? 0 : 1
let distances[i1][i2] = min([
\ distances[i1 - 1][i2 ] + 1,
\ distances[i1 ][i2 - 1] + 1,
\ distances[i1 - 1][i2 - 1] + cost,
\])
endfor
endfor
return distances[length1][length2]
endfunction
function! s:padding_by_displaywidth(expr, width, float) abort
let padding_char = ' '
let n = a:width - strdisplaywidth(a:expr)
if n <= 0
let n = 0
endif
if a:float < 0
return a:expr . repeat(padding_char, n)
elseif 0 < a:float
return repeat(padding_char, n) . a:expr
else
if n % 2 is 0
return repeat(padding_char, n / 2) . a:expr . repeat(padding_char, n / 2)
else
return repeat(padding_char, (n - 1) / 2) . a:expr . repeat(padding_char, (n - 1) / 2) . padding_char
endif
endif
endfunction
function! s:split_by_displaywidth(expr, width, float, is_wrap) abort
if a:width is 0
return ['']
endif
let lines = []
let cs = split(a:expr, '\zs')
let cs_index = 0
let text = ''
while cs_index < len(cs)
if cs[cs_index] is# "\n"
let text = s:padding_by_displaywidth(text, a:width, a:float)
let lines += [text]
let text = ''
else
let w = strdisplaywidth(text . cs[cs_index])
if w < a:width
let text .= cs[cs_index]
elseif a:width < w
let text = s:padding_by_displaywidth(text, a:width, a:float)
else
let text .= cs[cs_index]
endif
if a:width <= w
let lines += [text]
let text = ''
if a:is_wrap
if a:width < w
if a:width < strdisplaywidth(cs[cs_index])
while get(cs, cs_index, "\n") isnot# "\n"
let cs_index += 1
endwhile
continue
else
let text = cs[cs_index]
endif
endif
else
while get(cs, cs_index, "\n") isnot# "\n"
let cs_index += 1
endwhile
continue
endif
endif
endif
let cs_index += 1
endwhile
if !empty(text)
let lines += [ s:padding_by_displaywidth(text, a:width, a:float) ]
endif
return lines
endfunction
function! s:hash(str) abort
if exists('*sha256')
return sha256(a:str)
else
" This gives up sha256ing but just adds up char with index.
let sum = 0
for i in range(len(a:str))
let sum += char2nr(a:str[i]) * (i + 1)
endfor
return printf('%x', sum)
endif
endfunction
function! s:truncate(str, width) abort
" Original function is from mattn.
" http://github.com/mattn/googlereader-vim/tree/master
if a:str =~# '^[\x00-\x7f]*$'
return len(a:str) < a:width
\ ? printf('%-' . a:width . 's', a:str)
\ : strpart(a:str, 0, a:width)
endif
let ret = a:str
let width = s:wcswidth(a:str)
if width > a:width
let ret = s:strwidthpart(ret, a:width)
let width = s:wcswidth(ret)
endif
if width < a:width
let ret .= repeat(' ', a:width - width)
endif
return ret
endfunction
function! s:truncate_skipping(str, max, footer_width, separator) abort
let width = s:wcswidth(a:str)
if width <= a:max
let ret = a:str
else
let header_width = a:max - s:wcswidth(a:separator) - a:footer_width
let ret = s:strwidthpart(a:str, header_width) . a:separator
\ . s:strwidthpart_reverse(a:str, a:footer_width)
endif
return s:truncate(ret, a:max)
endfunction
function! s:strwidthpart(str, width) abort
let str = tr(a:str, "\t", ' ')
let vcol = a:width + 2
return matchstr(str, '.*\%<' . (vcol < 0 ? 0 : vcol) . 'v')
endfunction
function! s:strwidthpart_reverse(str, width) abort
let str = tr(a:str, "\t", ' ')
let vcol = s:wcswidth(str) - a:width
return matchstr(str, '\%>' . (vcol < 0 ? 0 : vcol) . 'v.*')
endfunction
if v:version >= 703
" Use builtin function.
let s:wcswidth = function('strwidth')
else
function! s:wcswidth(str) abort
if a:str =~# '^[\x00-\x7f]*$'
return strlen(a:str)
endif
let mx_first = '^\(.\)'
let str = a:str
let width = 0
while 1
let ucs = char2nr(substitute(str, mx_first, '\1', ''))
if ucs == 0
break
endif
let width += s:_wcwidth(ucs)
let str = substitute(str, mx_first, '', '')
endwhile
return width
endfunction
" UTF-8 only.
function! s:_wcwidth(ucs) abort
let ucs = a:ucs
if (ucs >= 0x1100
\ && (ucs <= 0x115f
\ || ucs == 0x2329
\ || ucs == 0x232a
\ || (ucs >= 0x2e80 && ucs <= 0xa4cf
\ && ucs != 0x303f)
\ || (ucs >= 0xac00 && ucs <= 0xd7a3)
\ || (ucs >= 0xf900 && ucs <= 0xfaff)
\ || (ucs >= 0xfe30 && ucs <= 0xfe6f)
\ || (ucs >= 0xff00 && ucs <= 0xff60)
\ || (ucs >= 0xffe0 && ucs <= 0xffe6)
\ || (ucs >= 0x20000 && ucs <= 0x2fffd)
\ || (ucs >= 0x30000 && ucs <= 0x3fffd)
\ ))
return 2
endif
return 1
endfunction
endif
function! s:remove_ansi_sequences(text) abort
return substitute(a:text, '\e\[\%(\%(\d\+;\)*\d\+\)\?[mK]', '', 'g')
endfunction
function! s:escape_pattern(str) abort
" escape characters for no-magic
return escape(a:str, '^$~.*[]\')
endfunction
function! s:unescape_pattern(str) abort
" unescape characters for no-magic
return s:unescape(a:str, '^$~.*[]\')
endfunction
function! s:unescape(str, chars) abort
let chars = map(split(a:chars, '\zs'), 'escape(v:val, ''^$~.*[]\'')')
return substitute(a:str, '\\\(' . join(chars, '\|') . '\)', '\1', 'g')
endfunction
function! s:iconv(expr, from, to) abort
if a:from ==# '' || a:to ==# '' || a:from ==? a:to
return a:expr
endif
let result = iconv(a:expr, a:from, a:to)
return empty(result) ? a:expr : result
endfunction
" NOTE:
" A definition of a TEXT file is "A file that contains characters organized
" into one or more lines."
" A definition of a LINE is "A sequence of zero or more non- <newline>s
" plus a terminating <newline>"
" That's why {stdin} always ends with <newline> ideally. However, there are
" some programs which does not follow the POSIX rule and a Vim's way to join
" List into TEXT; join({text}, "\n"); does not add <newline> to the end of
" the last line.
" That's why add a trailing <newline> if it does not exist.
" REF:
" http://pubs.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap03.html#tag_03_392
" http://pubs.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap03.html#tag_03_205
" :help split()
" NOTE:
" it does nothing if the text is a correct POSIX text
function! s:repair_posix_text(text, ...) abort
let newline = get(a:000, 0, "\n")
return a:text =~# '\n$' ? a:text : a:text . newline
endfunction
" NOTE:
" A definition of a TEXT file is "A file that contains characters organized
" into one or more lines."
" A definition of a LINE is "A sequence of zero or more non- <newline>s
" plus a terminating <newline>"
" REF:
" http://pubs.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap03.html#tag_03_392
" http://pubs.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap03.html#tag_03_205
function! s:join_posix_lines(lines, ...) abort
let newline = get(a:000, 0, "\n")
return join(a:lines, newline) . newline
endfunction
" NOTE:
" A definition of a TEXT file is "A file that contains characters organized
" into one or more lines."
" A definition of a LINE is "A sequence of zero or more non- <newline>s
" plus a terminating <newline>"
" TEXT into List; split({text}, '\r\?\n', 1); add an extra empty line at the
" end of List because the end of TEXT ends with <newline> and keepempty=1 is
" specified. (btw. keepempty=0 cannot be used because it will remove
" emptylines in the head and the tail).
" That's why removing a trailing <newline> before proceeding to 'split' is required
" REF:
" http://pubs.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap03.html#tag_03_392
" http://pubs.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap03.html#tag_03_205
function! s:split_posix_text(text, ...) abort
let newline = get(a:000, 0, '\r\?\n')
let text = substitute(a:text, newline . '$', '', '')
return split(text, newline, 1)
endfunction
let &cpo = s:save_cpo
unlet s:save_cpo
" vim:set et ts=2 sts=2 sw=2 tw=0:

View File

@ -0,0 +1,423 @@
" ___vital___
" NOTE: lines between '" ___vital___' is generated by :Vitalize.
" Do not mofidify the code nor insert new lines before '" ___vital___'
if v:version > 703 || v:version == 703 && has('patch1170')
function! vital#_grammarous#OptionParser#import() abort
return map({'_vital_depends': '', 'new': '', '_vital_loaded': ''}, 'function("s:" . v:key)')
endfunction
else
function! s:_SID() abort
return matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze__SID$')
endfunction
execute join(['function! vital#_grammarous#OptionParser#import() abort', printf("return map({'_vital_depends': '', 'new': '', '_vital_loaded': ''}, \"function('<SNR>%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n")
delfunction s:_SID
endif
" ___vital___
let s:save_cpo = &cpo
set cpo&vim
let s:_STRING_TYPE = type('')
let s:_LIST_TYPE = type([])
let s:_DICT_TYPE = type({})
let s:_NUM_TYPE = type(0)
function! s:_vital_loaded(V) abort
let s:L = a:V.import('Data.List')
endfunction
function! s:_vital_depends() abort
return ['Data.List']
endfunction
let s:_PRESET_COMPLETER = {}
function! s:_PRESET_COMPLETER.file(optlead, cmdline, cursorpos) abort
let candidates = glob(a:optlead . '*', 0, 1)
if a:optlead =~# '^\~'
let home_matcher = '^' . expand('~') . '/'
call map(candidates, "substitute(v:val, home_matcher, '~/', '')")
endif
call map(candidates, "escape(isdirectory(v:val) ? v:val.'/' : v:val, ' \\')")
return candidates
endfunction
function! s:_make_option_description_for_help(opt) abort
let extra = ''
if has_key(a:opt, 'default_value')
let extra .= 'DEFAULT: ' . string(a:opt.default_value) . ', '
endif
if get(a:opt, 'required_option', 0)
let extra .= 'REQUIRED, '
endif
if has_key(a:opt, 'pattern_option')
let extra .= 'PATTERN: ' . string(a:opt.pattern_option) . ', '
endif
let extra = substitute(extra, ', $', '', '')
if extra !=# ''
let extra = ' (' . extra . ')'
endif
return a:opt.description . extra
endfunction
function! s:_make_option_definition_for_help(opt) abort
let key = a:opt.definition
if has_key(a:opt, 'short_option_definition')
let key .= ', ' . a:opt.short_option_definition
endif
return key
endfunction
function! s:_extract_special_opts(argc, argv) abort
let ret = {'specials' : {}}
if a:argc <= 0
return ret
endif
let ret.q_args = a:argv[0]
for arg in a:argv[1:]
let arg_type = type(arg)
if arg_type == s:_LIST_TYPE
let ret.specials.__range__ = arg
elseif arg_type == type(0)
let ret.specials.__count__ = arg
elseif arg_type == s:_STRING_TYPE
if arg ==# '!'
let ret.specials.__bang__ = arg
elseif arg !=# ''
let ret.specials.__reg__ = arg
endif
endif
unlet arg
endfor
return ret
endfunction
function! s:_make_args(cmd_args) abort
let type = type(a:cmd_args)
if type == s:_STRING_TYPE
return split(a:cmd_args)
elseif type == s:_LIST_TYPE
return map(copy(a:cmd_args), 'type(v:val) == s:_STRING_TYPE ? v:val : string(v:val)')
else
throw 'vital: OptionParser: Invalid type: first argument of parse() should be string or list of string'
endif
endfunction
function! s:_expand_short_option(arg, options) abort
let short_opt = matchstr(a:arg, '^-[^- =]\>')
for [name, value] in items(a:options)
if get(value, 'short_option_definition', '') ==# short_opt
return substitute(a:arg, short_opt, '--' . name, '')
endif
endfor
return a:arg
endfunction
function! s:_check_extra_option(parsed_args, options) abort
for [name, option] in items(a:options)
if has_key(option, 'default_value') && ! has_key(a:parsed_args, name)
let a:parsed_args[name] = option.default_value
endif
if get(option, 'required_option', 0) && ! has_key(a:parsed_args, name)
throw 'vital: OptionParser: parameter is required: ' . name
endif
if has_key(option, 'pattern_option') && has_key(a:parsed_args, name) && a:parsed_args[name] !~# option.pattern_option
throw 'vital: OptionParser: parameter doesn''t match pattern: ' . name . ' ' . option.pattern_option
endif
endfor
endfunction
function! s:_parse_arg(arg, options) abort
" if --no-hoge pattern
if a:arg =~# '^--no-[^= ]\+'
" get hoge from --no-hoge
let key = matchstr(a:arg, '^--no-\zs[^= ]\+')
if has_key(a:options, key) && has_key(a:options[key], 'no')
return [key, 0]
endif
" if --hoge pattern
elseif a:arg =~# '^--[^= ]\+$'
" get hoge from --hoge
let key = matchstr(a:arg, '^--\zs[^= ]\+')
if has_key(a:options, key)
if has_key(a:options[key], 'has_value')
throw 'vital: OptionParser: Must specify value for option: ' . key
endif
return [key, 1]
endif
" if --hoge=poyo pattern
else
" get hoge from --hoge=poyo
let key = matchstr(a:arg, '^--\zs[^= ]\+')
if has_key(a:options, key)
" get poyo from --hoge=poyo
return [key, matchstr(a:arg, '^--[^= ]\+=\zs\S\+$')]
endif
endif
return a:arg
endfunction
function! s:_parse_args(cmd_args, options) abort
let parsed_args = {}
let unknown_args = []
let args = s:_make_args(a:cmd_args)
for arg in args
" replace short option with long option if short option is available
if arg =~# '^-[^- =]\>'
let arg = s:_expand_short_option(arg, a:options)
endif
" check if arg is --[no-]hoge[=VALUE]
if arg !~# '^--\%(no-\)\=[^= ]\+\%(=\S\+\)\=$'
call add(unknown_args, arg)
continue
endif
let parsed_arg = s:_parse_arg(arg, a:options)
if type(parsed_arg) == s:_LIST_TYPE
let parsed_args[parsed_arg[0]] = parsed_arg[1]
else
call add(unknown_args, parsed_arg)
endif
unlet parsed_arg
endfor
return [parsed_args, unknown_args]
endfunction
let s:_DEFAULT_PARSER = {'options' : {}}
function! s:_DEFAULT_PARSER.help() abort
let definitions = map(values(self.options), '[s:_make_option_definition_for_help(v:val), s:_make_option_description_for_help(v:val)]')
let key_width = len(s:L.max_by(definitions, 'len(v:val[0])')[0])
return "Options:\n" .
\ join(map(definitions, '
\ " " . v:val[0] .
\ repeat(" ", key_width - len(v:val[0])) . " : " .
\ v:val[1]
\ '), "\n")
endfunction
function! s:_DEFAULT_PARSER.parse(...) abort
let opts = s:_extract_special_opts(a:0, a:000)
if ! has_key(opts, 'q_args')
return opts.specials
endif
if ! get(self, 'disable_auto_help', 0)
\ && opts.q_args ==# '--help'
\ && ! has_key(self.options, 'help')
echo self.help()
return extend(opts.specials, {'help' : 1, '__unknown_args__' : []})
endif
let parsed_args = s:_parse_args(opts.q_args, self.options)
let ret = parsed_args[0]
call s:_check_extra_option(ret, self.options)
call extend(ret, opts.specials)
let ret.__unknown_args__ = parsed_args[1]
return ret
endfunction
function! s:_DEFAULT_PARSER.on(def, desc, ...) abort
if a:0 > 1
throw 'vital: OptionParser: Wrong number of arguments: ' . a:0 + 2 . ' for 2 or 3'
endif
" get hoge and huga from --hoge=huga
let matched = matchlist(a:def, '^--\([^= ]\+\)\(=\S\+\)\=$')[1:2]
if len(matched) != 2
throw 'vital: OptionParser: Invalid option "' . a:def . '"'
endif
let [name, value] = matched
let has_value = value !=# ''
let no = name =~# '^\[no-]'
if no
let name = matchstr(name, '^\[no-]\zs.\+')
endif
if name ==# ''
throw 'vital: OptionParser: Option of key is invalid: ' . a:def
endif
let self.options[name] = {'definition' : a:def, 'description' : a:desc}
if no
let self.options[name].no = 1
endif
if has_value
let self.options[name].has_value = 1
endif
" if short option is specified
if a:0 == 1
if type(a:1) == type({})
if has_key(a:1, 'short')
if (a:1.short !~# '^-[[:alnum:]]\>')
throw 'vital: OptionParser: Invalid short option: ' . a:1.short
endif
let self.options[name].short_option_definition = a:1.short
endif
if has_key(a:1, 'default')
let self.options[name].default_value = a:1.default
endif
if has_key(a:1, 'completion')
if type(a:1.completion) == s:_STRING_TYPE
let self.options[name].completion = s:_PRESET_COMPLETER[a:1.completion]
else
let self.options[name].completion = a:1.completion
endif
endif
if has_key(a:1, 'required')
if a:1.required isnot 0 && a:1.required isnot 1
throw 'vital: OptionParser: Invalid required option: ' . string(a:1.required)
endif
let self.options[name].required_option = a:1.required
endif
if has_key(a:1, 'pattern')
try
call match('', a:1.pattern)
catch
throw printf('vital: OptionParser: Invalid pattern option: exception="%s" pattern="%s"', v:exception, a:1.pattern)
endtry
let self.options[name].pattern_option = a:1.pattern
endif
else
let self.options[name].default_value = a:1
endif
endif
return self
endfunction
function! s:_complete_long_option(arglead, options) abort
let candidates = []
for [name, option] in items(a:options)
let has_value = get(option, 'has_value', 0)
call add(candidates, '--' . name . (has_value ? '=' : ''))
if get(option, 'no', 0)
call add(candidates, '--no-' . name . (has_value ? '=' : ''))
endif
endfor
let lead_pattern = '^' . a:arglead
return filter(candidates, 'v:val =~# lead_pattern')
endfunction
function! s:_complete_short_option(arglead, options) abort
let candidates = []
for option in values(a:options)
let has_value = get(option, 'has_value', 0)
if has_key(option, 'short_option_definition')
call add(candidates, option.short_option_definition . (has_value ? '=' : ''))
if get(option, 'no', 0)
call add(candidates, '-no' . option.short_option_definition . (has_value ? '=' : ''))
endif
endif
endfor
let lead_pattern = '^' . a:arglead
return filter(candidates, 'v:val =~# lead_pattern')
endfunction
function! s:_complete_user_specified_option(options, arglead, cmdline, cursorpos) abort
let lead = matchstr(a:arglead, '=\zs.*$')
let name = matchstr(a:arglead, '^--\zs[^=]\+')
if ! has_key(a:options, name) || ! has_key(a:options[name], 'completion')
return []
endif
return a:options[name].completion(lead, a:cmdline, a:cursorpos)
endfunction
function! s:_complete_user_specified_short_option(options, arglead, cmdline, cursorpos) abort
let lead = matchstr(a:arglead, '=\zs.*$')
let def = matchstr(a:arglead, '^-[^-=]')
for option in values(a:options)
if has_key(option, 'short_option_definition')
\ && option.short_option_definition ==# def
\ && has_key(option, 'completion')
return option.completion(lead, a:cmdline, a:cursorpos)
endif
endfor
return []
endfunction
function! s:_complete_unknown_option(Completer, arglead, cmdline, cursorpos) abort
if type(a:Completer) == s:_STRING_TYPE
return s:_PRESET_COMPLETER[a:Completer](a:arglead, a:cmdline, a:cursorpos)
else
return a:Completer(a:arglead, a:cmdline, a:cursorpos)
endif
endfunction
function! s:_DEFAULT_PARSER.complete(arglead, cmdline, cursorpos) abort
if a:arglead =~# '^--[^=]*$'
" when long option
return s:_complete_long_option(a:arglead, self.options)
elseif a:arglead =~# '^-[^-=]\?$'
" when short option
return s:_complete_short_option(a:arglead, self.options)
elseif a:arglead =~# '^--.\+=.*$'
let prefix = matchstr(a:arglead, '^.\+=')
return map(
\ s:_complete_user_specified_option(self.options, a:arglead, a:cmdline, a:cursorpos),
\ 'prefix . v:val'
\ )
elseif a:arglead =~# '^-[^-=]=.*$'
let prefix = matchstr(a:arglead, '^-[^-=]=')
return map(
\ s:_complete_user_specified_short_option(self.options, a:arglead, a:cmdline, a:cursorpos),
\ 'prefix . v:val'
\ )
elseif has_key(self, 'unknown_options_completion')
return s:_complete_unknown_option(self.unknown_options_completion, a:arglead, a:cmdline, a:cursorpos)
endif
return []
endfunction
function! s:_DEFAULT_PARSER.complete_greedily(arglead, cmdline, cursorpos) abort
if a:arglead =~# '^--.\+=.*$'
let prefix = matchstr(a:arglead, '^.\+=')
return map(
\ s:_complete_user_specified_option(self.options, a:arglead, a:cmdline, a:cursorpos),
\ 'prefix . v:val'
\ )
elseif a:arglead =~# '^-[^-=]=.*$'
let prefix = matchstr(a:arglead, '^-[^-=]=')
return map(
\ s:_complete_user_specified_short_option(self.options, a:arglead, a:cmdline, a:cursorpos),
\ 'prefix . v:val'
\ )
endif
let long_opts = s:_complete_long_option(a:arglead, self.options)
if has_key(self, 'unknown_options_completion')
return long_opts + s:_complete_unknown_option(
\ self.unknown_options_completion,
\ a:arglead,
\ a:cmdline,
\ a:cursorpos
\ )
else
return long_opts
endif
endfunction
function! s:new() abort
return deepcopy(s:_DEFAULT_PARSER)
endfunction
let &cpo = s:save_cpo
unlet s:save_cpo
" vim:set et ts=2 sts=2 sw=2 tw=0:

View File

@ -0,0 +1,430 @@
" ___vital___
" NOTE: lines between '" ___vital___' is generated by :Vitalize.
" Do not mofidify the code nor insert new lines before '" ___vital___'
if v:version > 703 || v:version == 703 && has('patch1170')
function! vital#_grammarous#Prelude#import() abort
return map({'escape_pattern': '', 'is_funcref': '', 'path2directory': '', 'wcswidth': '', 'is_string': '', 'input_helper': '', 'is_number': '', 'is_cygwin': '', 'path2project_directory': '', 'strwidthpart_reverse': '', 'input_safe': '', 'is_list': '', 'truncate_skipping': '', 'glob': '', 'truncate': '', 'is_dict': '', 'set_default': '', 'is_numeric': '', 'getchar_safe': '', 'substitute_path_separator': '', 'is_mac': '', 'strwidthpart': '', 'getchar': '', 'is_unix': '', 'is_windows': '', 'globpath': '', 'escape_file_searching': '', 'is_float': '', 'smart_execute_command': ''}, 'function("s:" . v:key)')
endfunction
else
function! s:_SID() abort
return matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze__SID$')
endfunction
execute join(['function! vital#_grammarous#Prelude#import() abort', printf("return map({'escape_pattern': '', 'is_funcref': '', 'path2directory': '', 'wcswidth': '', 'is_string': '', 'input_helper': '', 'is_number': '', 'is_cygwin': '', 'path2project_directory': '', 'strwidthpart_reverse': '', 'input_safe': '', 'is_list': '', 'truncate_skipping': '', 'glob': '', 'truncate': '', 'is_dict': '', 'set_default': '', 'is_numeric': '', 'getchar_safe': '', 'substitute_path_separator': '', 'is_mac': '', 'strwidthpart': '', 'getchar': '', 'is_unix': '', 'is_windows': '', 'globpath': '', 'escape_file_searching': '', 'is_float': '', 'smart_execute_command': ''}, \"function('<SNR>%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n")
delfunction s:_SID
endif
" ___vital___
let s:save_cpo = &cpo
set cpo&vim
if v:version > 703 ||
\ (v:version == 703 && has('patch465'))
function! s:glob(expr) abort
return glob(a:expr, 1, 1)
endfunction
else
function! s:glob(expr) abort
return split(glob(a:expr, 1), '\n')
endfunction
endif
if v:version > 704 ||
\ (v:version == 704 && has('patch279'))
function! s:globpath(path, expr) abort
return globpath(a:path, a:expr, 1, 1)
endfunction
else
function! s:globpath(path, expr) abort
return split(globpath(a:path, a:expr, 1), '\n')
endfunction
endif
" Wrapper functions for type().
" NOTE: __TYPE_FLOAT = -1 when -float.
" this doesn't match to anything.
if has('patch-7.4.2071')
let [
\ s:__TYPE_NUMBER,
\ s:__TYPE_STRING,
\ s:__TYPE_FUNCREF,
\ s:__TYPE_LIST,
\ s:__TYPE_DICT,
\ s:__TYPE_FLOAT] = [
\ v:t_number,
\ v:t_string,
\ v:t_func,
\ v:t_list,
\ v:t_dict,
\ v:t_float]
else
let [
\ s:__TYPE_NUMBER,
\ s:__TYPE_STRING,
\ s:__TYPE_FUNCREF,
\ s:__TYPE_LIST,
\ s:__TYPE_DICT,
\ s:__TYPE_FLOAT] = [
\ type(3),
\ type(''),
\ type(function('tr')),
\ type([]),
\ type({}),
\ has('float') ? type(str2float('0')) : -1]
endif
" Number or Float
function! s:is_numeric(Value) abort
let _ = type(a:Value)
return _ ==# s:__TYPE_NUMBER
\ || _ ==# s:__TYPE_FLOAT
endfunction
" Number
function! s:is_number(Value) abort
return type(a:Value) ==# s:__TYPE_NUMBER
endfunction
" String
function! s:is_string(Value) abort
return type(a:Value) ==# s:__TYPE_STRING
endfunction
" Funcref
function! s:is_funcref(Value) abort
return type(a:Value) ==# s:__TYPE_FUNCREF
endfunction
" List
function! s:is_list(Value) abort
return type(a:Value) ==# s:__TYPE_LIST
endfunction
" Dictionary
function! s:is_dict(Value) abort
return type(a:Value) ==# s:__TYPE_DICT
endfunction
" Float
function! s:is_float(Value) abort
return type(a:Value) ==# s:__TYPE_FLOAT
endfunction
function! s:truncate_skipping(str, max, footer_width, separator) abort
call s:_warn_deprecated('truncate_skipping', 'Data.String.truncate_skipping')
let width = s:wcswidth(a:str)
if width <= a:max
let ret = a:str
else
let header_width = a:max - s:wcswidth(a:separator) - a:footer_width
let ret = s:strwidthpart(a:str, header_width) . a:separator
\ . s:strwidthpart_reverse(a:str, a:footer_width)
endif
return s:truncate(ret, a:max)
endfunction
function! s:truncate(str, width) abort
" Original function is from mattn.
" http://github.com/mattn/googlereader-vim/tree/master
call s:_warn_deprecated('truncate', 'Data.String.truncate')
if a:str =~# '^[\x00-\x7f]*$'
return len(a:str) < a:width ?
\ printf('%-'.a:width.'s', a:str) : strpart(a:str, 0, a:width)
endif
let ret = a:str
let width = s:wcswidth(a:str)
if width > a:width
let ret = s:strwidthpart(ret, a:width)
let width = s:wcswidth(ret)
endif
if width < a:width
let ret .= repeat(' ', a:width - width)
endif
return ret
endfunction
function! s:strwidthpart(str, width) abort
call s:_warn_deprecated('strwidthpart', 'Data.String.strwidthpart')
if a:width <= 0
return ''
endif
let ret = a:str
let width = s:wcswidth(a:str)
while width > a:width
let char = matchstr(ret, '.$')
let ret = ret[: -1 - len(char)]
let width -= s:wcswidth(char)
endwhile
return ret
endfunction
function! s:strwidthpart_reverse(str, width) abort
call s:_warn_deprecated('strwidthpart_reverse', 'Data.String.strwidthpart_reverse')
if a:width <= 0
return ''
endif
let ret = a:str
let width = s:wcswidth(a:str)
while width > a:width
let char = matchstr(ret, '^.')
let ret = ret[len(char) :]
let width -= s:wcswidth(char)
endwhile
return ret
endfunction
if v:version >= 703
" Use builtin function.
function! s:wcswidth(str) abort
call s:_warn_deprecated('wcswidth', 'Data.String.wcswidth')
return strwidth(a:str)
endfunction
else
function! s:wcswidth(str) abort
call s:_warn_deprecated('wcswidth', 'Data.String.wcswidth')
if a:str =~# '^[\x00-\x7f]*$'
return strlen(a:str)
end
let mx_first = '^\(.\)'
let str = a:str
let width = 0
while 1
let ucs = char2nr(substitute(str, mx_first, '\1', ''))
if ucs == 0
break
endif
let width += s:_wcwidth(ucs)
let str = substitute(str, mx_first, '', '')
endwhile
return width
endfunction
" UTF-8 only.
function! s:_wcwidth(ucs) abort
let ucs = a:ucs
if (ucs >= 0x1100
\ && (ucs <= 0x115f
\ || ucs == 0x2329
\ || ucs == 0x232a
\ || (ucs >= 0x2e80 && ucs <= 0xa4cf
\ && ucs != 0x303f)
\ || (ucs >= 0xac00 && ucs <= 0xd7a3)
\ || (ucs >= 0xf900 && ucs <= 0xfaff)
\ || (ucs >= 0xfe30 && ucs <= 0xfe6f)
\ || (ucs >= 0xff00 && ucs <= 0xff60)
\ || (ucs >= 0xffe0 && ucs <= 0xffe6)
\ || (ucs >= 0x20000 && ucs <= 0x2fffd)
\ || (ucs >= 0x30000 && ucs <= 0x3fffd)
\ ))
return 2
endif
return 1
endfunction
endif
let s:is_windows = has('win16') || has('win32') || has('win64') || has('win95')
let s:is_cygwin = has('win32unix')
let s:is_mac = !s:is_windows && !s:is_cygwin
\ && (has('mac') || has('macunix') || has('gui_macvim') ||
\ (!isdirectory('/proc') && executable('sw_vers')))
let s:is_unix = has('unix')
function! s:is_windows() abort
return s:is_windows
endfunction
function! s:is_cygwin() abort
return s:is_cygwin
endfunction
function! s:is_mac() abort
return s:is_mac
endfunction
function! s:is_unix() abort
return s:is_unix
endfunction
function! s:_warn_deprecated(name, alternative) abort
try
echohl Error
echomsg 'Prelude.' . a:name . ' is deprecated! Please use ' . a:alternative . ' instead.'
finally
echohl None
endtry
endfunction
function! s:smart_execute_command(action, word) abort
execute a:action . ' ' . (a:word ==# '' ? '' : '`=a:word`')
endfunction
function! s:escape_file_searching(buffer_name) abort
return escape(a:buffer_name, '*[]?{}, ')
endfunction
function! s:escape_pattern(str) abort
call s:_warn_deprecated(
\ 'escape_pattern',
\ 'Data.String.escape_pattern',
\)
return escape(a:str, '~"\.^$[]*')
endfunction
function! s:getchar(...) abort
let c = call('getchar', a:000)
return type(c) == type(0) ? nr2char(c) : c
endfunction
function! s:getchar_safe(...) abort
let c = s:input_helper('getchar', a:000)
return type(c) == type('') ? c : nr2char(c)
endfunction
function! s:input_safe(...) abort
return s:input_helper('input', a:000)
endfunction
function! s:input_helper(funcname, args) abort
let success = 0
if inputsave() !=# success
throw 'vital: Prelude: inputsave() failed'
endif
try
return call(a:funcname, a:args)
finally
if inputrestore() !=# success
throw 'vital: Prelude: inputrestore() failed'
endif
endtry
endfunction
function! s:set_default(var, val) abort
if !exists(a:var) || type({a:var}) != type(a:val)
let {a:var} = a:val
endif
endfunction
function! s:substitute_path_separator(path) abort
return s:is_windows ? substitute(a:path, '\\', '/', 'g') : a:path
endfunction
function! s:path2directory(path) abort
return s:substitute_path_separator(isdirectory(a:path) ? a:path : fnamemodify(a:path, ':p:h'))
endfunction
function! s:_path2project_directory_git(path) abort
let parent = a:path
while 1
let path = parent . '/.git'
if isdirectory(path) || filereadable(path)
return parent
endif
let next = fnamemodify(parent, ':h')
if next == parent
return ''
endif
let parent = next
endwhile
endfunction
function! s:_path2project_directory_svn(path) abort
let search_directory = a:path
let directory = ''
let find_directory = s:escape_file_searching(search_directory)
let d = finddir('.svn', find_directory . ';')
if d ==# ''
return ''
endif
let directory = fnamemodify(d, ':p:h:h')
" Search parent directories.
let parent_directory = s:path2directory(
\ fnamemodify(directory, ':h'))
if parent_directory !=# ''
let d = finddir('.svn', parent_directory . ';')
if d !=# ''
let directory = s:_path2project_directory_svn(parent_directory)
endif
endif
return directory
endfunction
function! s:_path2project_directory_others(vcs, path) abort
let vcs = a:vcs
let search_directory = a:path
let find_directory = s:escape_file_searching(search_directory)
let d = finddir(vcs, find_directory . ';')
if d ==# ''
return ''
endif
return fnamemodify(d, ':p:h:h')
endfunction
function! s:path2project_directory(path, ...) abort
let is_allow_empty = get(a:000, 0, 0)
let search_directory = s:path2directory(a:path)
let directory = ''
" Search VCS directory.
for vcs in ['.git', '.bzr', '.hg', '.svn']
if vcs ==# '.git'
let directory = s:_path2project_directory_git(search_directory)
elseif vcs ==# '.svn'
let directory = s:_path2project_directory_svn(search_directory)
else
let directory = s:_path2project_directory_others(vcs, search_directory)
endif
if directory !=# ''
break
endif
endfor
" Search project file.
if directory ==# ''
for d in ['build.xml', 'prj.el', '.project', 'pom.xml', 'package.json',
\ 'Makefile', 'configure', 'Rakefile', 'NAnt.build',
\ 'P4CONFIG', 'tags', 'gtags']
let d = findfile(d, s:escape_file_searching(search_directory) . ';')
if d !=# ''
let directory = fnamemodify(d, ':p:h')
break
endif
endfor
endif
if directory ==# ''
" Search /src/ directory.
let base = s:substitute_path_separator(search_directory)
if base =~# '/src/'
let directory = base[: strridx(base, '/src/') + 3]
endif
endif
if directory ==# '' && !is_allow_empty
" Use original path.
let directory = search_directory
endif
return s:substitute_path_separator(directory)
endfunction
let &cpo = s:save_cpo
unlet s:save_cpo
" vim:set et ts=2 sts=2 sw=2 tw=0:

View File

@ -0,0 +1,184 @@
" ___vital___
" NOTE: lines between '" ___vital___' is generated by :Vitalize.
" Do not mofidify the code nor insert new lines before '" ___vital___'
if v:version > 703 || v:version == 703 && has('patch1170')
function! vital#_grammarous#Process#import() abort
return map({'shellescape': '', 'has_vimproc': '', 'system': '', 'iconv': '', 'spawn': '', 'get_last_status': ''}, 'function("s:" . v:key)')
endfunction
else
function! s:_SID() abort
return matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze__SID$')
endfunction
execute join(['function! vital#_grammarous#Process#import() abort', printf("return map({'shellescape': '', 'has_vimproc': '', 'system': '', 'iconv': '', 'spawn': '', 'get_last_status': ''}, \"function('<SNR>%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n")
delfunction s:_SID
endif
" ___vital___
" TODO: move all comments to doc file.
"
"
" FIXME: This module name should be Vital.System ?
" But the name has been already taken.
let s:save_cpo = &cpo
set cpo&vim
" FIXME: Unfortunately, can't use s:_vital_loaded() for this purpose.
" Because these variables are used when this script file is loaded.
let s:is_windows = has('win16') || has('win32') || has('win64') || has('win95')
let s:is_unix = has('unix')
" As of 7.4.122, the system()'s 1st argument is converted internally by Vim.
" Note that Patch 7.4.122 does not convert system()'s 2nd argument and
" return-value. We must convert them manually.
let s:need_trans = v:version < 704 || (v:version == 704 && !has('patch122'))
let s:TYPE_DICT = type({})
let s:TYPE_LIST = type([])
let s:TYPE_STRING = type('')
function! s:spawn(expr, ...) abort
let shellslash = 0
if s:is_windows
let shellslash = &l:shellslash
setlocal noshellslash
endif
try
if type(a:expr) is s:TYPE_LIST
let special = 1
let cmdline = join(map(a:expr, 'shellescape(v:val, special)'), ' ')
elseif type(a:expr) is s:TYPE_STRING
let cmdline = a:expr
if a:0 && a:1
" for :! command
let cmdline = substitute(cmdline, '\([!%#]\|<[^<>]\+>\)', '\\\1', 'g')
endif
else
throw 'vital: Process: invalid argument (value type:' . type(a:expr) . ')'
endif
if s:is_windows
silent execute '!start' cmdline
else
silent execute '!' cmdline '&'
endif
finally
if s:is_windows
let &l:shellslash = shellslash
endif
endtry
return ''
endfunction
" iconv() wrapper for safety.
function! s:iconv(expr, from, to) abort
if a:from ==# '' || a:to ==# '' || a:from ==? a:to
return a:expr
endif
let result = iconv(a:expr, a:from, a:to)
return result !=# '' ? result : a:expr
endfunction
" Check vimproc.
function! s:has_vimproc() abort
if !exists('s:exists_vimproc')
try
call vimproc#version()
let s:exists_vimproc = 1
catch
let s:exists_vimproc = 0
endtry
endif
return s:exists_vimproc
endfunction
" * {command} [, {input} [, {timeout}]]
" * {command} [, {dict}]
" {dict} = {
" use_vimproc: bool,
" input: string,
" timeout: bool,
" background: bool,
" }
function! s:system(str, ...) abort
" Process optional arguments at first
" because use_vimproc is required later
" for a:str argument.
let input = ''
let use_vimproc = s:has_vimproc()
let background = 0
let args = []
if a:0 ==# 1
" {command} [, {dict}]
" a:1 = {dict}
if type(a:1) is s:TYPE_DICT
if has_key(a:1, 'use_vimproc')
let use_vimproc = a:1.use_vimproc
endif
if has_key(a:1, 'input')
let args += [s:iconv(a:1.input, &encoding, 'char')]
endif
if use_vimproc && has_key(a:1, 'timeout')
" ignores timeout unless you have vimproc.
let args += [a:1.timeout]
endif
if has_key(a:1, 'background')
let background = a:1.background
endif
elseif type(a:1) is s:TYPE_STRING
let args += [s:iconv(a:1, &encoding, 'char')]
else
throw 'vital: Process: invalid argument (value type:' . type(a:1) . ')'
endif
elseif a:0 >= 2
" {command} [, {input} [, {timeout}]]
" a:000 = [{input} [, {timeout}]]
let [input; rest] = a:000
let input = s:iconv(input, &encoding, 'char')
let args += [input] + rest
endif
" Process a:str argument.
if type(a:str) is s:TYPE_LIST
let expr = use_vimproc ? '"''" . v:val . "''"' : 's:shellescape(v:val)'
let command = join(map(copy(a:str), expr), ' ')
elseif type(a:str) is s:TYPE_STRING
let command = a:str
else
throw 'vital: Process: invalid argument (value type:' . type(a:str) . ')'
endif
if s:need_trans
let command = s:iconv(command, &encoding, 'char')
endif
let args = [command] + args
if background && (use_vimproc || !s:is_windows)
if has('nvim')
throw "vital: Process: neovim's system() doesn't support background(&) process (cmdline:" . a:str . ')'
endif
let args[0] = args[0] . ' &'
endif
let funcname = use_vimproc ? 'vimproc#system' : 'system'
let output = call(funcname, args)
let output = s:iconv(output, 'char', &encoding)
return output
endfunction
function! s:get_last_status() abort
return s:has_vimproc() ?
\ vimproc#get_last_status() : v:shell_error
endfunction
if s:is_windows
function! s:shellescape(command) abort
return substitute(a:command, '[&()[\]{}^=;!''+,`~]', '^\0', 'g')
endfunction
else
function! s:shellescape(...) abort
return call('shellescape', a:000)
endfunction
endif
let &cpo = s:save_cpo
unlet s:save_cpo
" vim:set et ts=2 sts=2 sw=2 tw=0:

View File

@ -0,0 +1,665 @@
" ___vital___
" NOTE: lines between '" ___vital___' is generated by :Vitalize.
" Do not mofidify the code nor insert new lines before '" ___vital___'
if v:version > 703 || v:version == 703 && has('patch1170')
function! vital#_grammarous#Web#HTTP#import() abort
return map({'decodeURI': '', '_vital_depends': '', 'parseHeader': '', 'encodeURIComponent': '', 'encodeURI': '', 'escape': '', 'post': '', 'get': '', 'request': '', '_vital_loaded': ''}, 'function("s:" . v:key)')
endfunction
else
function! s:_SID() abort
return matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze__SID$')
endfunction
execute join(['function! vital#_grammarous#Web#HTTP#import() abort', printf("return map({'decodeURI': '', '_vital_depends': '', 'parseHeader': '', 'encodeURIComponent': '', 'encodeURI': '', 'escape': '', 'post': '', 'get': '', 'request': '', '_vital_loaded': ''}, \"function('<SNR>%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n")
delfunction s:_SID
endif
" ___vital___
let s:save_cpo = &cpo
set cpo&vim
function! s:_vital_loaded(V) abort
let s:V = a:V
let s:Prelude = s:V.import('Prelude')
let s:Process = s:V.import('Process')
let s:String = s:V.import('Data.String')
endfunction
function! s:_vital_depends() abort
return ['Prelude', 'Data.String', 'Process']
endfunction
function! s:__urlencode_char(c) abort
return printf('%%%02X', char2nr(a:c))
endfunction
function! s:decodeURI(str) abort
let ret = a:str
let ret = substitute(ret, '+', ' ', 'g')
let ret = substitute(ret, '%\(\x\x\)', '\=printf("%c", str2nr(submatch(1), 16))', 'g')
return ret
endfunction
function! s:escape(str) abort
let result = ''
for i in range(len(a:str))
if a:str[i] =~# '^[a-zA-Z0-9_.~-]$'
let result .= a:str[i]
else
let result .= s:__urlencode_char(a:str[i])
endif
endfor
return result
endfunction
function! s:encodeURI(items) abort
let ret = ''
if s:Prelude.is_dict(a:items)
for key in sort(keys(a:items))
if strlen(ret)
let ret .= '&'
endif
let ret .= key . '=' . s:encodeURI(a:items[key])
endfor
elseif s:Prelude.is_list(a:items)
for item in sort(a:items)
if strlen(ret)
let ret .= '&'
endif
let ret .= item
endfor
else
let ret = s:escape(a:items)
endif
return ret
endfunction
function! s:encodeURIComponent(items) abort
let ret = ''
if s:Prelude.is_dict(a:items)
for key in sort(keys(a:items))
if strlen(ret) | let ret .= '&' | endif
let ret .= key . '=' . s:encodeURIComponent(a:items[key])
endfor
elseif s:Prelude.is_list(a:items)
for item in sort(a:items)
if strlen(ret) | let ret .= '&' | endif
let ret .= item
endfor
else
let items = iconv(a:items, &enc, 'utf-8')
let len = strlen(items)
let i = 0
while i < len
let ch = items[i]
if ch =~# '[0-9A-Za-z-._~!''()*]'
let ret .= ch
elseif ch ==# ' '
let ret .= '+'
else
let ret .= '%' . substitute('0' . s:String.nr2hex(char2nr(ch)), '^.*\(..\)$', '\1', '')
endif
let i = i + 1
endwhile
endif
return ret
endfunction
function! s:request(...) abort
let settings = s:_build_settings(a:000)
let settings.method = toupper(settings.method)
if !has_key(settings, 'url')
throw 'vital: Web.HTTP: "url" parameter is required.'
endif
if !s:Prelude.is_list(settings.client)
let settings.client = [settings.client]
endif
let client = s:_get_client(settings)
if empty(client)
throw 'vital: Web.HTTP: Available client not found: '
\ . string(settings.client)
endif
if has_key(settings, 'contentType')
let settings.headers['Content-Type'] = settings.contentType
endif
if has_key(settings, 'param')
if s:Prelude.is_dict(settings.param)
let getdatastr = s:encodeURI(settings.param)
else
let getdatastr = settings.param
endif
if strlen(getdatastr)
let settings.url .= '?' . getdatastr
endif
endif
if has_key(settings, 'data')
let settings.data = s:_postdata(settings.data)
let settings.headers['Content-Length'] = len(join(settings.data, "\n"))
endif
let settings._file = {}
let responses = client.request(settings)
for file in values(settings._file)
if filereadable(file)
call delete(file)
endif
endfor
call map(responses, 's:_build_response(v:val[0], v:val[1])')
return s:_build_last_response(responses)
endfunction
function! s:get(url, ...) abort
let settings = {
\ 'url': a:url,
\ 'param': a:0 > 0 ? a:1 : {},
\ 'headers': a:0 > 1 ? a:2 : {},
\ }
return s:request(settings)
endfunction
function! s:post(url, ...) abort
let settings = {
\ 'url': a:url,
\ 'data': a:0 > 0 ? a:1 : {},
\ 'headers': a:0 > 1 ? a:2 : {},
\ 'method': a:0 > 2 ? a:3 : 'POST',
\ }
return s:request(settings)
endfunction
function! s:_readfile(file) abort
if filereadable(a:file)
return join(readfile(a:file, 'b'), "\n")
endif
return ''
endfunction
function! s:_make_postfile(data) abort
let fname = s:_tempname()
call writefile(a:data, fname, 'b')
return fname
endfunction
function! s:_tempname() abort
return tr(tempname(), '\', '/')
endfunction
function! s:_postdata(data) abort
if s:Prelude.is_dict(a:data)
return [s:encodeURI(a:data)]
elseif s:Prelude.is_list(a:data)
return a:data
else
return split(a:data, "\n")
endif
endfunction
function! s:_build_response(header, content) abort
let response = {
\ 'header' : a:header,
\ 'content': a:content,
\ 'status': 0,
\ 'statusText': '',
\ 'success': 0,
\ }
if !empty(a:header)
let status_line = get(a:header, 0)
let matched = matchlist(status_line, '^HTTP/1\.\d\s\+\(\d\+\)\s\+\(.*\)')
if !empty(matched)
let [status, status_text] = matched[1 : 2]
let response.status = status - 0
let response.statusText = status_text
let response.success = status =~# '^2'
call remove(a:header, 0)
endif
endif
return response
endfunction
function! s:_build_last_response(responses) abort
let all_headers = []
for response in a:responses
call extend(all_headers, response.header)
endfor
let last_response = remove(a:responses, -1)
let last_response.redirectInfo = a:responses
let last_response.allHeaders = all_headers
return last_response
endfunction
function! s:_build_settings(args) abort
let settings = {
\ 'method': 'GET',
\ 'headers': {},
\ 'client': ['python', 'curl', 'wget'],
\ 'maxRedirect': 20,
\ 'retry': 1,
\ }
let args = copy(a:args)
if len(args) == 0
throw 'vital: Web.HTTP: request() needs one or more arguments.'
endif
if s:Prelude.is_dict(args[-1])
call extend(settings, remove(args, -1))
endif
if len(args) == 2
let settings.method = remove(args, 0)
endif
if !empty(args)
let settings.url = args[0]
endif
return settings
endfunction
function! s:_make_header_args(headdata, option, quote) abort
let args = ''
for [key, value] in items(a:headdata)
if s:Prelude.is_windows()
let value = substitute(value, '"', '"""', 'g')
endif
let args .= ' ' . a:option . a:quote . key . ': ' . value . a:quote
endfor
return args
endfunction
function! s:parseHeader(headers) abort
" FIXME: User should be able to specify the treatment method of the duplicate item.
let header = {}
for h in a:headers
let matched = matchlist(h, '^\([^:]\+\):\s*\(.*\)$')
if !empty(matched)
let [name, value] = matched[1 : 2]
let header[name] = value
endif
endfor
return header
endfunction
" Clients
function! s:_get_client(settings) abort
for name in a:settings.client
if has_key(s:clients, name) && s:clients[name].available(a:settings)
return s:clients[name]
endif
endfor
return {}
endfunction
let s:clients = {}
let s:clients.python = {}
function! s:clients.python.available(settings) abort
if !has('python')
return 0
endif
if has_key(a:settings, 'outputFile')
" 'outputFile' is not supported yet
return 0
endif
if get(a:settings, 'retry', 0) != 1
" 'retry' is not supported yet
return 0
endif
if has_key(a:settings, 'authMethod')
return 0
endif
return 1
endfunction
function! s:clients.python.request(settings) abort
" TODO: retry, outputFile
let responses = []
python << endpython
try:
class DummyClassForLocalScope:
def main():
try:
from StringIO import StringIO
except ImportError:
from io import StringIO
import vim, urllib2, socket, gzip
responses = vim.bindeval('responses')
class CustomHTTPRedirectHandler(urllib2.HTTPRedirectHandler):
def __init__(self, max_redirect):
self.max_redirect = max_redirect
def redirect_request(self, req, fp, code, msg, headers, newurl):
if self.max_redirect == 0:
return None
if 0 < self.max_redirect:
self.max_redirect -= 1
header_list = filter(None, str(headers).split("\r\n"))
responses.extend([[[status(code, msg)] + header_list, fp.read()]])
return urllib2.HTTPRedirectHandler.redirect_request(self, req, fp, code, msg, headers, newurl)
def vimlist2str(list):
if not list:
return None
return "\n".join([s.replace("\n", "\0") for s in list])
def status(code, msg):
return "HTTP/1.0 %d %s\r\n" % (code, msg)
def access():
settings = vim.eval('a:settings')
data = vimlist2str(settings.get('data'))
timeout = settings.get('timeout')
if timeout:
timeout = float(timeout)
request_headers = settings.get('headers')
max_redirect = int(settings.get('maxRedirect'))
director = urllib2.build_opener(CustomHTTPRedirectHandler(max_redirect))
if settings.has_key('username'):
passman = urllib2.HTTPPasswordMgrWithDefaultRealm()
passman.add_password(
None,
settings['url'],
settings['username'],
settings.get('password', ''))
basicauth = urllib2.HTTPBasicAuthHandler(passman)
digestauth = urllib2.HTTPDigestAuthHandler(passman)
director.add_handler(basicauth)
director.add_handler(digestauth)
req = urllib2.Request(settings['url'], data, request_headers)
req.get_method = lambda: settings['method']
default_timeout = socket.getdefaulttimeout()
try:
# for Python 2.5 or before
socket.setdefaulttimeout(timeout)
res = director.open(req, timeout=timeout)
except urllib2.HTTPError as res:
pass
except urllib2.URLError:
return ('', '')
except socket.timeout:
return ('', '')
finally:
socket.setdefaulttimeout(default_timeout)
st = status(res.code, res.msg)
response_headers = st + ''.join(res.info().headers)
response_body = res.read()
gzip_decompress = settings.get('gzipDecompress', False)
if gzip_decompress:
buf = StringIO(response_body)
f = gzip.GzipFile(fileobj=buf)
response_body = f.read()[:-1]
return (response_headers, response_body)
(header, body) = access()
responses.extend([[header.split("\r\n"), body]])
main()
raise RuntimeError("Exit from local scope")
except RuntimeError as exception:
if exception.args != ("Exit from local scope",):
raise exception
endpython
return responses
endfunction
let s:clients.curl = {}
let s:clients.curl.errcode = {}
let s:clients.curl.errcode[1] = 'Unsupported protocol. This build of curl has no support for this protocol.'
let s:clients.curl.errcode[2] = 'Failed to initialize.'
let s:clients.curl.errcode[3] = 'URL malformed. The syntax was not correct.'
let s:clients.curl.errcode[4] = 'A feature or option that was needed to perform the desired request was not enabled or was explicitly disabled at buildtime. To make curl able to do this, you probably need another build of libcurl!'
let s:clients.curl.errcode[5] = 'Couldn''t resolve proxy. The given proxy host could not be resolved.'
let s:clients.curl.errcode[6] = 'Couldn''t resolve host. The given remote host was not resolved.'
let s:clients.curl.errcode[7] = 'Failed to connect to host.'
let s:clients.curl.errcode[8] = 'FTP weird server reply. The server sent data curl couldn''t parse.'
let s:clients.curl.errcode[9] = 'FTP access denied. The server denied login or denied access to the particular resource or directory you wanted to reach. Most often you tried to change to a directory that doesn''t exist on the server.'
let s:clients.curl.errcode[11] = 'FTP weird PASS reply. Curl couldn''t parse the reply sent to the PASS request.'
let s:clients.curl.errcode[13] = 'FTP weird PASV reply, Curl couldn''t parse the reply sent to the PASV request.'
let s:clients.curl.errcode[14] = 'FTP weird 227 format. Curl couldn''t parse the 227-line the server sent.'
let s:clients.curl.errcode[15] = 'FTP can''t get host. Couldn''t resolve the host IP we got in the 227-line.'
let s:clients.curl.errcode[17] = 'FTP couldn''t set binary. Couldn''t change transfer method to binary.'
let s:clients.curl.errcode[18] = 'Partial file. Only a part of the file was transferred.'
let s:clients.curl.errcode[19] = 'FTP couldn''t download/access the given file, the RETR (or similar) command failed.'
let s:clients.curl.errcode[21] = 'FTP quote error. A quote command returned error from the server.'
let s:clients.curl.errcode[22] = 'HTTP page not retrieved. The requested url was not found or returned another error with the HTTP error code being 400 or above. This return code only appears if -f, --fail is used.'
let s:clients.curl.errcode[23] = 'Write error. Curl couldn''t write data to a local filesystem or similar.'
let s:clients.curl.errcode[25] = 'FTP couldn''t STOR file. The server denied the STOR operation, used for FTP uploading.'
let s:clients.curl.errcode[26] = 'Read error. Various reading problems.'
let s:clients.curl.errcode[27] = 'Out of memory. A memory allocation request failed.'
let s:clients.curl.errcode[28] = 'Operation timeout. The specified time-out period was reached according to the conditions.'
let s:clients.curl.errcode[30] = 'FTP PORT failed. The PORT command failed. Not all FTP servers support the PORT command, try doing a transfer using PASV instead!'
let s:clients.curl.errcode[31] = 'FTP couldn''t use REST. The REST command failed. This command is used for resumed FTP transfers.'
let s:clients.curl.errcode[33] = 'HTTP range error. The range "command" didn''t work.'
let s:clients.curl.errcode[34] = 'HTTP post error. Internal post-request generation error.'
let s:clients.curl.errcode[35] = 'SSL connect error. The SSL handshaking failed.'
let s:clients.curl.errcode[36] = 'FTP bad download resume. Couldn''t continue an earlier aborted download.'
let s:clients.curl.errcode[37] = 'FILE couldn''t read file. Failed to open the file. Permissions?'
let s:clients.curl.errcode[38] = 'LDAP cannot bind. LDAP bind operation failed.'
let s:clients.curl.errcode[39] = 'LDAP search failed.'
let s:clients.curl.errcode[41] = 'Function not found. A required LDAP function was not found.'
let s:clients.curl.errcode[42] = 'Aborted by callback. An application told curl to abort the operation.'
let s:clients.curl.errcode[43] = 'Internal error. A function was called with a bad parameter.'
let s:clients.curl.errcode[45] = 'Interface error. A specified outgoing interface could not be used.'
let s:clients.curl.errcode[47] = 'Too many redirects. When following redirects, curl hit the maximum amount.'
let s:clients.curl.errcode[48] = 'Unknown option specified to libcurl. This indicates that you passed a weird option to curl that was passed on to libcurl and rejected. Read up in the manual!'
let s:clients.curl.errcode[49] = 'Malformed telnet option.'
let s:clients.curl.errcode[51] = 'The peer''s SSL certificate or SSH MD5 fingerprint was not OK.'
let s:clients.curl.errcode[52] = 'The server didn''t reply anything, which here is considered an error.'
let s:clients.curl.errcode[53] = 'SSL crypto engine not found.'
let s:clients.curl.errcode[54] = 'Cannot set SSL crypto engine as default.'
let s:clients.curl.errcode[55] = 'Failed sending network data.'
let s:clients.curl.errcode[56] = 'Failure in receiving network data.'
let s:clients.curl.errcode[58] = 'Problem with the local certificate.'
let s:clients.curl.errcode[59] = 'Couldn''t use specified SSL cipher.'
let s:clients.curl.errcode[60] = 'Peer certificate cannot be authenticated with known CA certificates.'
let s:clients.curl.errcode[61] = 'Unrecognized transfer encoding.'
let s:clients.curl.errcode[62] = 'Invalid LDAP URL.'
let s:clients.curl.errcode[63] = 'Maximum file size exceeded.'
let s:clients.curl.errcode[64] = 'Requested FTP SSL level failed.'
let s:clients.curl.errcode[65] = 'Sending the data requires a rewind that failed.'
let s:clients.curl.errcode[66] = 'Failed to initialise SSL Engine.'
let s:clients.curl.errcode[67] = 'The user name, password, or similar was not accepted and curl failed to log in.'
let s:clients.curl.errcode[68] = 'File not found on TFTP server.'
let s:clients.curl.errcode[69] = 'Permission problem on TFTP server.'
let s:clients.curl.errcode[70] = 'Out of disk space on TFTP server.'
let s:clients.curl.errcode[71] = 'Illegal TFTP operation.'
let s:clients.curl.errcode[72] = 'Unknown TFTP transfer ID.'
let s:clients.curl.errcode[73] = 'File already exists (TFTP).'
let s:clients.curl.errcode[74] = 'No such user (TFTP).'
let s:clients.curl.errcode[75] = 'Character conversion failed.'
let s:clients.curl.errcode[76] = 'Character conversion functions required.'
let s:clients.curl.errcode[77] = 'Problem with reading the SSL CA cert (path? access rights?).'
let s:clients.curl.errcode[78] = 'The resource referenced in the URL does not exist.'
let s:clients.curl.errcode[79] = 'An unspecified error occurred during the SSH session.'
let s:clients.curl.errcode[80] = 'Failed to shut down the SSL connection.'
let s:clients.curl.errcode[82] = 'Could not load CRL file, missing or wrong format (added in 7.19.0).'
let s:clients.curl.errcode[83] = 'Issuer check failed (added in 7.19.0).'
let s:clients.curl.errcode[84] = 'The FTP PRET command failed'
let s:clients.curl.errcode[85] = 'RTSP: mismatch of CSeq numbers'
let s:clients.curl.errcode[86] = 'RTSP: mismatch of Session Identifiers'
let s:clients.curl.errcode[87] = 'unable to parse FTP file list'
let s:clients.curl.errcode[88] = 'FTP chunk callback reported error'
let s:clients.curl.errcode[89] = 'No connection available, the session will be queued'
let s:clients.curl.errcode[90] = 'SSL public key does not matched pinned public key'
function! s:clients.curl.available(settings) abort
return executable(self._command(a:settings))
endfunction
function! s:clients.curl._command(settings) abort
return get(get(a:settings, 'command', {}), 'curl', 'curl')
endfunction
function! s:clients.curl.request(settings) abort
let quote = s:_quote()
let command = self._command(a:settings)
let a:settings._file.header = s:_tempname()
let command .= ' --dump-header ' . quote . a:settings._file.header . quote
let has_output_file = has_key(a:settings, 'outputFile')
if has_output_file
let output_file = a:settings.outputFile
else
let output_file = s:_tempname()
let a:settings._file.content = output_file
endif
let command .= ' --output ' . quote . output_file . quote
if has_key(a:settings, 'gzipDecompress') && a:settings.gzipDecompress
let command .= ' --compressed '
endif
let command .= ' -L -s -k -X ' . a:settings.method
let command .= ' --max-redirs ' . a:settings.maxRedirect
let command .= s:_make_header_args(a:settings.headers, '-H ', quote)
let timeout = get(a:settings, 'timeout', '')
let command .= ' --retry ' . a:settings.retry
if timeout =~# '^\d\+$'
let command .= ' --max-time ' . timeout
endif
if has_key(a:settings, 'username')
let auth = a:settings.username . ':' . get(a:settings, 'password', '')
let auth = escape(auth, quote)
if has_key(a:settings, 'authMethod')
if index(['basic', 'digest', 'ntlm', 'negotiate'], a:settings.authMethod) == -1
throw 'vital: Web.HTTP: Invalid authorization method: ' . a:settings.authMethod
endif
let method = a:settings.authMethod
else
let method = 'anyauth'
endif
let command .= ' --' . method . ' --user ' . quote . auth . quote
endif
if has_key(a:settings, 'data')
let a:settings._file.post = s:_make_postfile(a:settings.data)
let command .= ' --data-binary @' . quote . a:settings._file.post . quote
endif
let command .= ' ' . quote . a:settings.url . quote
call s:Process.system(command)
let retcode = s:Process.get_last_status()
let headerstr = s:_readfile(a:settings._file.header)
let header_chunks = split(headerstr, "\r\n\r\n")
let headers = map(header_chunks, 'split(v:val, "\r\n")')
if retcode != 0 && empty(headers)
if has_key(s:clients.curl.errcode, retcode)
throw 'vital: Web.HTTP: ' . s:clients.curl.errcode[retcode]
else
throw 'vital: Web.HTTP: Unknown error code has occured in curl: code=' . retcode
endif
endif
if !empty(headers)
let responses = map(headers, '[v:val, ""]')
else
let responses = [[[], '']]
endif
if has_output_file
let content = ''
else
let content = s:_readfile(output_file)
endif
let responses[-1][1] = content
return responses
endfunction
let s:clients.wget = {}
let s:clients.wget.errcode = {}
let s:clients.wget.errcode[1] = 'Generic error code.'
let s:clients.wget.errcode[2] = 'Parse error---for instance, when parsing command-line options, the .wgetrc or .netrc...'
let s:clients.wget.errcode[3] = 'File I/O error.'
let s:clients.wget.errcode[4] = 'Network failure.'
let s:clients.wget.errcode[5] = 'SSL verification failure.'
let s:clients.wget.errcode[6] = 'Username/password authentication failure.'
let s:clients.wget.errcode[7] = 'Protocol errors.'
let s:clients.wget.errcode[8] = 'Server issued an error response.'
function! s:clients.wget.available(settings) abort
if has_key(a:settings, 'authMethod')
return 0
endif
return executable(self._command(a:settings))
endfunction
function! s:clients.wget._command(settings) abort
return get(get(a:settings, 'command', {}), 'wget', 'wget')
endfunction
function! s:clients.wget.request(settings) abort
let quote = s:_quote()
let command = self._command(a:settings)
let method = a:settings.method
if method ==# 'HEAD'
let command .= ' --spider'
elseif method !=# 'GET' && method !=# 'POST'
let a:settings.headers['X-HTTP-Method-Override'] = a:settings.method
endif
let a:settings._file.header = s:_tempname()
let command .= ' -o ' . quote . a:settings._file.header . quote
let has_output_file = has_key(a:settings, 'outputFile')
if has_output_file
let output_file = a:settings.outputFile
else
let output_file = s:_tempname()
let a:settings._file.content = output_file
endif
let command .= ' -O ' . quote . output_file . quote
let command .= ' --server-response -q -L '
let command .= ' --max-redirect=' . a:settings.maxRedirect
let command .= s:_make_header_args(a:settings.headers, '--header=', quote)
let timeout = get(a:settings, 'timeout', '')
let command .= ' --tries=' . a:settings.retry
if timeout =~# '^\d\+$'
let command .= ' --timeout=' . timeout
endif
if has_key(a:settings, 'username')
let command .= ' --http-user=' . quote . escape(a:settings.username, quote) . quote
endif
if has_key(a:settings, 'password')
let command .= ' --http-password=' . quote . escape(a:settings.password, quote) . quote
endif
let command .= ' ' . quote . a:settings.url . quote
if has_key(a:settings, 'data')
let a:settings._file.post = s:_make_postfile(a:settings.data)
let command .= ' --post-file=' . quote . a:settings._file.post . quote
endif
call s:Process.system(command)
let retcode = s:Process.get_last_status()
if filereadable(a:settings._file.header)
let header_lines = readfile(a:settings._file.header, 'b')
call map(header_lines, 'matchstr(v:val, "^\\s*\\zs.*")')
let headerstr = join(header_lines, "\r\n")
let header_chunks = split(headerstr, '\r\n\zeHTTP/1\.\d')
let headers = map(header_chunks, 'split(v:val, "\r\n")')
let responses = map(headers, '[v:val, ""]')
else
let headers = []
let responses = [[[], '']]
endif
if has_key(s:clients.wget.errcode, retcode) && empty(headers)
throw 'vital: Web.HTTP: ' . s:clients.wget.errcode[retcode]
endif
if has_output_file
let content = ''
else
let content = s:_readfile(output_file)
endif
let responses[-1][1] = content
return responses
endfunction
function! s:_quote() abort
return &shellxquote ==# '"' ? "'" : '"'
endfunction
let &cpo = s:save_cpo
unlet s:save_cpo
" vim:set et ts=2 sts=2 sw=2 tw=0:

View File

@ -0,0 +1,315 @@
" ___vital___
" NOTE: lines between '" ___vital___' is generated by :Vitalize.
" Do not mofidify the code nor insert new lines before '" ___vital___'
if v:version > 703 || v:version == 703 && has('patch1170')
function! vital#_grammarous#Web#XML#import() abort
return map({'parseFile': '', '_vital_depends': '', 'createElement': '', 'parse': '', 'decodeEntityReference': '', 'encodeEntityReference': '', 'parseURL': '', '_vital_loaded': ''}, 'function("s:" . v:key)')
endfunction
else
function! s:_SID() abort
return matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze__SID$')
endfunction
execute join(['function! vital#_grammarous#Web#XML#import() abort', printf("return map({'parseFile': '', '_vital_depends': '', 'createElement': '', 'parse': '', 'decodeEntityReference': '', 'encodeEntityReference': '', 'parseURL': '', '_vital_loaded': ''}, \"function('<SNR>%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n")
delfunction s:_SID
endif
" ___vital___
let s:save_cpo = &cpo
set cpo&vim
function! s:_vital_loaded(V) abort
let s:V = a:V
let s:S = s:V.import('Data.String')
let s:H = s:V.import('Web.HTTP')
endfunction
function! s:_vital_depends() abort
return ['Data.String', 'Web.HTTP']
endfunction
let s:__template = { 'name': '', 'attr': {}, 'child': [] }
function! s:decodeEntityReference(str) abort
let str = a:str
let str = substitute(str, '&gt;', '>', 'g')
let str = substitute(str, '&lt;', '<', 'g')
"let str = substitute(str, '&quot;', '"', 'g')
"let str = substitute(str, '&apos;', "'", 'g')
"let str = substitute(str, '&nbsp;', ' ', 'g')
"let str = substitute(str, '&yen;', '\&#65509;', 'g')
let str = substitute(str, '&#x\([0-9a-fA-F]\+\);', '\=s:S.nr2enc_char("0x".submatch(1))', 'g')
let str = substitute(str, '&#\(\d\+\);', '\=s:S.nr2enc_char(submatch(1))', 'g')
let str = substitute(str, '&amp;', '\&', 'g')
return str
endfunction
function! s:encodeEntityReference(str) abort
let str = a:str
let str = substitute(str, '&', '\&amp;', 'g')
let str = substitute(str, '>', '\&gt;', 'g')
let str = substitute(str, '<', '\&lt;', 'g')
let str = substitute(str, '"', '\&#34;', 'g')
"let str = substitute(str, "\n", '\&#x0d;', 'g')
"let str = substitute(str, '"', '&quot;', 'g')
"let str = substitute(str, "'", '&apos;', 'g')
"let str = substitute(str, ' ', '&nbsp;', 'g')
return str
endfunction
function! s:__matchNode(node, cond) abort
if type(a:cond) == 1 && a:node.name == a:cond
return 1
endif
if type(a:cond) == 2
return a:cond(a:node)
endif
if type(a:cond) == 3
let ret = 1
for R in a:cond
if !s:__matchNode(a:node, R) | let ret = 0 | endif
unlet R
endfor
return ret
endif
if type(a:cond) == 4
for k in keys(a:cond)
if has_key(a:node.attr, k) && a:node.attr[k] == a:cond[k] | return 1 | endif
endfor
endif
return 0
endfunction
function! s:__template.childNode(...) dict abort
for c in self.child
if type(c) == 4 && s:__matchNode(c, a:000)
return c
endif
unlet c
endfor
return {}
endfunction
function! s:__template.childNodes(...) dict abort
let ret = []
for c in self.child
if type(c) == 4 && s:__matchNode(c, a:000)
let ret += [c]
endif
unlet c
endfor
return ret
endfunction
function! s:__template.value(...) dict abort
if a:0
let self.child = a:000
return
endif
let ret = ''
for c in self.child
if type(c) <= 1 || type(c) == 5
let ret .= c
elseif type(c) == 4
let ret .= c.value()
endif
unlet c
endfor
return ret
endfunction
function! s:__template.find(...) dict abort
for c in self.child
if type(c) == 4
if s:__matchNode(c, a:000)
return c
endif
unlet! ret
let ret = c.find(a:000)
if !empty(ret)
return ret
endif
endif
unlet c
endfor
return {}
endfunction
function! s:__template.findAll(...) dict abort
let ret = []
for c in self.child
if type(c) == 4
if s:__matchNode(c, a:000)
call add(ret, c)
endif
let ret += c.findAll(a:000)
endif
unlet c
endfor
return ret
endfunction
function! s:__template.toString() dict abort
let xml = '<' . self.name
for attr in keys(self.attr)
let xml .= ' ' . attr . '="' . s:encodeEntityReference(self.attr[attr]) . '"'
endfor
if len(self.child)
let xml .= '>'
for c in self.child
if type(c) == 4
let xml .= c.toString()
elseif type(c) > 1
let xml .= s:encodeEntityReference(string(c))
else
let xml .= s:encodeEntityReference(c)
endif
unlet c
endfor
let xml .= '</' . self.name . '>'
else
let xml .= ' />'
endif
return xml
endfunction
function! s:createElement(name) abort
let node = deepcopy(s:__template)
let node.name = a:name
return node
endfunction
" @vimlint(EVL102, 1, l:content)
function! s:__parse_tree(ctx, top) abort
let node = a:top
let stack = [a:top]
" content accumulates the text only tags
let content = ''
let append_content_to_parent = 'if len(stack) && content != "" | call add(stack[-1].child, content) | let content ="" | endif'
let mx = '^\s*\(<?xml[^>]\+>\)'
if a:ctx['xml'] =~ mx
let match = matchstr(a:ctx['xml'], mx)
let a:ctx['xml'] = a:ctx['xml'][stridx(a:ctx['xml'], match) + len(match):]
let mx = 'encoding\s*=\s*["'']\{0,1}\([^"'' \t]\+\|[^"'']\+\)["'']\{0,1}'
let matches = matchlist(match, mx)
if len(matches)
let encoding = matches[1]
if encoding !=# '' && a:ctx['encoding'] ==# ''
let a:ctx['encoding'] = encoding
let a:ctx['xml'] = iconv(a:ctx['xml'], encoding, &encoding)
endif
endif
endif
" this regex matches
" 1) the remaining until the next tag begins
" 2) maybe closing "/" of tag name
" 3) tagname
" 4) the attributes of the text (optional)
" 5) maybe closing "/" (end of tag name)
" or
" 6) CDATA or ''
" 7) text content of CDATA
" or
" 8) comment
" (These numbers correspond to the indexes in matched list m)
let tag_mx = '^\(\_.\{-}\)\%(\%(<\(/\?\)\([^!/>[:space:]]\+\)\(\%([[:space:]]*[^/>=[:space:]]\+[[:space:]]*=[[:space:]]*\%([^"'' >\t]\+\|"[^"]*"\|''[^'']*''\)\|[[:space:]]\+[^/>=[:space:]]\+[[:space:]]*\)*\)[[:space:]]*\(/\?\)>\)\|\%(<!\[\(CDATA\)\[\(.\{-}\)\]\]>\)\|\(<!--.\{-}-->\)\)'
while a:ctx.xml !=# ''
let m = matchlist(a:ctx.xml, tag_mx)
if empty(m) | break | endif
let a:ctx.xml = a:ctx.xml[len(m[0]) :]
let is_end_tag = m[2] ==# '/' && m[5] ==# ''
let is_start_and_end_tag = m[2] ==# '' && m[5] ==# '/'
let tag_name = m[3]
let attrs = m[4]
if m[1] !=# ''
let content .= s:decodeEntityReference(m[1])
endif
if is_end_tag
" closing tag: pop from stack and continue at upper level
exec append_content_to_parent
if len(stack) " TODO: checking whether opened tag is exist.
call remove(stack, -1)
endif
continue
endif
" comment tag
if m[8] !=# ''
continue
endif
" if element is a CDATA
if m[6] !=# ''
let content .= m[7]
continue
endif
let node = deepcopy(s:__template)
let node.name = tag_name
let attr_mx = '\([^=[:space:]]\+\)\s*\%(=\s*''\([^'']*\)''\|=\s*"\([^"]*\)"\|=\s*\(\w\+\)\|\)'
while attrs !=# ''
let attr_match = matchlist(attrs, attr_mx)
if len(attr_match) == 0
break
endif
let name = attr_match[1]
let value = attr_match[2] !=# '' ? attr_match[2] : attr_match[3] !=# '' ? attr_match[3] : attr_match[4] !=# '' ? attr_match[4] : ''
if value ==# ''
let node.attr[name] = ''
else
let node.attr[name] = s:decodeEntityReference(value)
endif
let attrs = attrs[stridx(attrs, attr_match[0]) + len(attr_match[0]):]
endwhile
exec append_content_to_parent
if len(stack)
call add(stack[-1].child, node)
endif
if !is_start_and_end_tag
" opening tag, continue parsing its contents
call add(stack, node)
endif
endwhile
endfunction
" @vimlint(EVL102, 0, l:content)
function! s:parse(xml) abort
let top = deepcopy(s:__template)
let oldmaxmempattern = &maxmempattern
let oldmaxfuncdepth = &maxfuncdepth
let &maxmempattern = 2000000
let &maxfuncdepth = 2000
try
call s:__parse_tree({'xml': a:xml, 'encoding': ''}, top)
for node in top.child
if type(node) == 4
return node
endif
unlet node
endfor
finally
let &maxmempattern = oldmaxmempattern
let &maxfuncdepth = oldmaxfuncdepth
endtry
throw 'vital: Web.XML: Parse Error'
endfunction
function! s:parseFile(fname) abort
return s:parse(join(readfile(a:fname), "\n"))
endfunction
function! s:parseURL(url) abort
return s:parse(s:H.get(a:url).content)
endfunction
let &cpo = s:save_cpo
unlet s:save_cpo
" vim:set et ts=2 sts=2 sw=2 tw=0:

View File

@ -0,0 +1,339 @@
let s:plugin_name = expand('<sfile>:t:r')
let s:vital_base_dir = expand('<sfile>:h')
let s:project_root = expand('<sfile>:h:h:h')
let s:is_vital_vim = s:plugin_name is# 'vital'
let s:loaded = {}
let s:cache_sid = {}
" function() wrapper
if v:version > 703 || v:version == 703 && has('patch1170')
function! s:_function(fstr) abort
return function(a:fstr)
endfunction
else
function! s:_SID() abort
return matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze__SID$')
endfunction
let s:_s = '<SNR>' . s:_SID() . '_'
function! s:_function(fstr) abort
return function(substitute(a:fstr, 's:', s:_s, 'g'))
endfunction
endif
function! vital#{s:plugin_name}#new() abort
return s:new(s:plugin_name)
endfunction
function! vital#{s:plugin_name}#import(...) abort
if !exists('s:V')
let s:V = s:new(s:plugin_name)
endif
return call(s:V.import, a:000, s:V)
endfunction
let s:Vital = {}
function! s:new(plugin_name) abort
let base = deepcopy(s:Vital)
let base._plugin_name = a:plugin_name
return base
endfunction
function! s:vital_files() abort
if !exists('s:vital_files')
let s:vital_files = map(
\ s:is_vital_vim ? s:_global_vital_files() : s:_self_vital_files(),
\ 'fnamemodify(v:val, ":p:gs?[\\\\/]?/?")')
endif
return copy(s:vital_files)
endfunction
let s:Vital.vital_files = s:_function('s:vital_files')
function! s:import(name, ...) abort dict
let target = {}
let functions = []
for a in a:000
if type(a) == type({})
let target = a
elseif type(a) == type([])
let functions = a
endif
unlet a
endfor
let module = self._import(a:name)
if empty(functions)
call extend(target, module, 'keep')
else
for f in functions
if has_key(module, f) && !has_key(target, f)
let target[f] = module[f]
endif
endfor
endif
return target
endfunction
let s:Vital.import = s:_function('s:import')
function! s:load(...) abort dict
for arg in a:000
let [name; as] = type(arg) == type([]) ? arg[: 1] : [arg, arg]
let target = split(join(as, ''), '\W\+')
let dict = self
let dict_type = type({})
while !empty(target)
let ns = remove(target, 0)
if !has_key(dict, ns)
let dict[ns] = {}
endif
if type(dict[ns]) == dict_type
let dict = dict[ns]
else
unlet dict
break
endif
endwhile
if exists('dict')
call extend(dict, self._import(name))
endif
unlet arg
endfor
return self
endfunction
let s:Vital.load = s:_function('s:load')
function! s:unload() abort dict
let s:loaded = {}
let s:cache_sid = {}
unlet! s:vital_files
endfunction
let s:Vital.unload = s:_function('s:unload')
function! s:exists(name) abort dict
if a:name !~# '\v^\u\w*%(\.\u\w*)*$'
throw 'vital: Invalid module name: ' . a:name
endif
return s:_module_path(a:name) isnot# ''
endfunction
let s:Vital.exists = s:_function('s:exists')
function! s:search(pattern) abort dict
let paths = s:_extract_files(a:pattern, self.vital_files())
let modules = sort(map(paths, 's:_file2module(v:val)'))
return s:_uniq(modules)
endfunction
let s:Vital.search = s:_function('s:search')
function! s:plugin_name() abort dict
return self._plugin_name
endfunction
let s:Vital.plugin_name = s:_function('s:plugin_name')
function! s:_self_vital_files() abort
let builtin = printf('%s/__%s__/', s:vital_base_dir, s:plugin_name)
let installed = printf('%s/_%s/', s:vital_base_dir, s:plugin_name)
let base = builtin . ',' . installed
return split(globpath(base, '**/*.vim', 1), "\n")
endfunction
function! s:_global_vital_files() abort
let pattern = 'autoload/vital/__*__/**/*.vim'
return split(globpath(&runtimepath, pattern, 1), "\n")
endfunction
function! s:_extract_files(pattern, files) abort
let tr = {'.': '/', '*': '[^/]*', '**': '.*'}
let target = substitute(a:pattern, '\.\|\*\*\?', '\=tr[submatch(0)]', 'g')
let regexp = printf('autoload/vital/[^/]\+/%s.vim$', target)
return filter(a:files, 'v:val =~# regexp')
endfunction
function! s:_file2module(file) abort
let filename = fnamemodify(a:file, ':p:gs?[\\/]?/?')
let tail = matchstr(filename, 'autoload/vital/_\w\+/\zs.*\ze\.vim$')
return join(split(tail, '[\\/]\+'), '.')
endfunction
" @param {string} name e.g. Data.List
function! s:_import(name) abort dict
if has_key(s:loaded, a:name)
return copy(s:loaded[a:name])
endif
let module = self._get_module(a:name)
if has_key(module, '_vital_created')
call module._vital_created(module)
endif
let export_module = filter(copy(module), 'v:key =~# "^\\a"')
" Cache module before calling module.vital_loaded() to avoid cyclic
" dependences but remove the cache if module._vital_loaded() fails.
" let s:loaded[a:name] = export_module
let s:loaded[a:name] = export_module
if has_key(module, '_vital_loaded')
try
call module._vital_loaded(vital#{s:plugin_name}#new())
catch
unlet s:loaded[a:name]
throw 'vital: fail to call ._vital_loaded(): ' . v:exception
endtry
endif
return copy(s:loaded[a:name])
endfunction
let s:Vital._import = s:_function('s:_import')
" s:_get_module() returns module object wihch has all script local functions.
function! s:_get_module(name) abort dict
let funcname = s:_import_func_name(self.plugin_name(), a:name)
if s:_exists_autoload_func_with_source(funcname)
return call(funcname, [])
else
return s:_get_builtin_module(a:name)
endif
endfunction
function! s:_get_builtin_module(name) abort
return s:sid2sfuncs(s:_module_sid(a:name))
endfunction
if s:is_vital_vim
" For vital.vim, we can use s:_get_builtin_module directly
let s:Vital._get_module = s:_function('s:_get_builtin_module')
else
let s:Vital._get_module = s:_function('s:_get_module')
endif
function! s:_import_func_name(plugin_name, module_name) abort
return printf('vital#_%s#%s#import', a:plugin_name, s:_dot_to_sharp(a:module_name))
endfunction
function! s:_module_sid(name) abort
let path = s:_module_path(a:name)
if !filereadable(path)
throw 'vital: module not found: ' . a:name
endif
let vital_dir = s:is_vital_vim ? '__\w\+__' : printf('_\{1,2}%s\%%(__\)\?', s:plugin_name)
let base = join([vital_dir, ''], '[/\\]\+')
let p = base . substitute('' . a:name, '\.', '[/\\\\]\\+', 'g')
let sid = s:_sid(path, p)
if !sid
call s:_source(path)
let sid = s:_sid(path, p)
if !sid
throw printf('vital: cannot get <SID> from path: %s', path)
endif
endif
return sid
endfunction
function! s:_module_path(name) abort
return get(s:_extract_files(a:name, s:vital_files()), 0, '')
endfunction
function! s:_module_sid_base_dir() abort
return s:is_vital_vim ? &rtp : s:project_root
endfunction
function! s:_dot_to_sharp(name) abort
return substitute(a:name, '\.', '#', 'g')
endfunction
" It will sources autoload file if a given func is not already defined.
function! s:_exists_autoload_func_with_source(funcname) abort
if exists('*' . a:funcname)
" Return true if a given func is already defined
return 1
endif
" source a file which may include a given func definition and try again.
let path = 'autoload/' . substitute(substitute(a:funcname, '#[^#]*$', '.vim', ''), '#', '/', 'g')
call s:_runtime(path)
return exists('*' . a:funcname)
endfunction
function! s:_runtime(path) abort
execute 'runtime' fnameescape(a:path)
endfunction
function! s:_source(path) abort
execute 'source' fnameescape(a:path)
endfunction
" @vimlint(EVL102, 1, l:_)
" @vimlint(EVL102, 1, l:__)
function! s:_sid(path, filter_pattern) abort
let unified_path = s:_unify_path(a:path)
if has_key(s:cache_sid, unified_path)
return s:cache_sid[unified_path]
endif
for line in filter(split(s:_redir(':scriptnames'), "\n"), 'v:val =~# a:filter_pattern')
let [_, sid, path; __] = matchlist(line, '^\s*\(\d\+\):\s\+\(.\+\)\s*$')
if s:_unify_path(path) is# unified_path
let s:cache_sid[unified_path] = sid
return s:cache_sid[unified_path]
endif
endfor
return 0
endfunction
function! s:_redir(cmd) abort
let [save_verbose, save_verbosefile] = [&verbose, &verbosefile]
set verbose=0 verbosefile=
redir => res
silent! execute a:cmd
redir END
let [&verbose, &verbosefile] = [save_verbose, save_verbosefile]
return res
endfunction
if filereadable(expand('<sfile>:r') . '.VIM') " is case-insensitive or not
let s:_unify_path_cache = {}
" resolve() is slow, so we cache results.
" Note: On windows, vim can't expand path names from 8.3 formats.
" So if getting full path via <sfile> and $HOME was set as 8.3 format,
" vital load duplicated scripts. Below's :~ avoid this issue.
function! s:_unify_path(path) abort
if has_key(s:_unify_path_cache, a:path)
return s:_unify_path_cache[a:path]
endif
let value = tolower(fnamemodify(resolve(fnamemodify(
\ a:path, ':p')), ':~:gs?[\\/]?/?'))
let s:_unify_path_cache[a:path] = value
return value
endfunction
else
function! s:_unify_path(path) abort
return resolve(fnamemodify(a:path, ':p:gs?[\\/]?/?'))
endfunction
endif
" copied and modified from Vim.ScriptLocal
let s:SNR = join(map(range(len("\<SNR>")), '"[\\x" . printf("%0x", char2nr("\<SNR>"[v:val])) . "]"'), '')
function! s:sid2sfuncs(sid) abort
let fs = split(s:_redir(printf(':function /^%s%s_', s:SNR, a:sid)), "\n")
let r = {}
let pattern = printf('\m^function\s<SNR>%d_\zs\w\{-}\ze(', a:sid)
for fname in map(fs, 'matchstr(v:val, pattern)')
let r[fname] = function(s:_sfuncname(a:sid, fname))
endfor
return r
endfunction
"" Return funcname of script local functions with SID
function! s:_sfuncname(sid, funcname) abort
return printf('<SNR>%s_%s', a:sid, a:funcname)
endfunction
if exists('*uniq')
function! s:_uniq(list) abort
return uniq(a:list)
endfunction
else
function! s:_uniq(list) abort
let i = len(a:list) - 1
while 0 < i
if a:list[i] ==# a:list[i - 1]
call remove(a:list, i)
endif
let i -= 1
endwhile
return a:list
endfunction
endif

View File

@ -0,0 +1,5 @@
grammarous
cb43c297bbeb2526226bd8775674a1b7c6006657
Web.XML
OptionParser

View File

@ -0,0 +1,71 @@
*grammarous.txt* A powerful grammar checker.
Author : rhysd <lin90162@yahoo.co.jp>
CONTENTS *vim-grammarous-contents*
Introduction |vim-grammarous-introduction|
Usage |vim-grammarous-usage|
License |vim-grammarous-license|
==============================================================================
INTRODUCTION *vim-grammarous-introduction*
*vim-grammarous* is a powerful grammar checker for Vim. Simply do
|:GrammarousCheck| to see the powerful checking. This plugin automatically
downloads LanguageTool, which requires Java 8+.
|vim-grammarous| uses job feature on Vim 8.0.27 (or later) or Neovim. You need
not to wait until the check has done.
==============================================================================
USAGE *vim-grammarous-usage*
*:GrammarousCheck* to check grammar in the buffer.
:[range]GrammarousCheck [--lang={lang}] [--(no-)preview] [--(no-)comments-only]
Execute the grammar checker for current buffer (when [range] is specified, the
target is a text in the range).
1. It makes LanguageTool check grammar (It takes a while)
2. It highlights the locations of detected grammar errors
3. When you move the cursor on a location of error, it automatically shows
the error with the information window.
Please do ":GrammarousCheck --help" to show more detail about the command.
In information window, some local mappings are available. Please enter "?" in
the window to show the help.
And *:GrammarousReset* resets the current check.
If you want to customize something (e.g. define mappings, disable rules),
please see https://github.com/rhysd/vim-grammarous for more detail.
==============================================================================
LICENSE *vim-grammarous-license*
Copyright (c) 2014 rhysd
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.
==============================================================================
vim:tw=78:colorcolumn=78:ts=8:ft=help:norl:et:fen:fdl=0:

View File

@ -0,0 +1,26 @@
if (exists('g:loaded_grammarous') && g:loaded_grammarous) || &cp
finish
endif
command! -nargs=* -bar -range=% -complete=customlist,grammarous#complete_opt GrammarousCheck call grammarous#check_current_buffer(<q-args>, [<line1>, <line2>])
command! -nargs=0 -bar GrammarousReset call grammarous#reset()
nnoremap <silent><Plug>(grammarous-move-to-info-window) :<C-u>call grammarous#create_and_jump_to_info_window_of(b:grammarous_result)<CR>
nnoremap <silent><Plug>(grammarous-open-info-window) :<C-u>call grammarous#create_update_info_window_of(b:grammarous_result)<CR>
nnoremap <silent><Plug>(grammarous-reset) :<C-u>call grammarous#reset()<CR>
nnoremap <silent><Plug>(grammarous-fixit) :<C-u>call grammarous#fixit(grammarous#get_error_at(getpos('.')[1 : 2], b:grammarous_result))<CR>
nnoremap <silent><Plug>(grammarous-fixall) :<C-u>call grammarous#fixall(b:grammarous_result)<CR>
nnoremap <silent><Plug>(grammarous-close-info-window) :<C-u>call grammarous#info_win#close()<CR>
nnoremap <silent><Plug>(grammarous-remove-error) :<C-u>call grammarous#remove_error_at(getpos('.')[1 : 2], b:grammarous_result)<CR>
nnoremap <silent><Plug>(grammarous-disable-rule) :<C-u>call grammarous#disable_rule_at(getpos('.')[1 : 2], b:grammarous_result)<CR>
nnoremap <silent><Plug>(grammarous-disable-category) :<C-u>call grammarous#disable_category_at(getpos('.')[1 : 2], b:grammarous_result)<CR>
nnoremap <silent><Plug>(grammarous-move-to-next-error) :<C-u>call grammarous#move_to_next_error(getpos('.')[1 : 2], b:grammarous_result)<CR>
nnoremap <silent><Plug>(grammarous-move-to-previous-error) :<C-u>call grammarous#move_to_previous_error(getpos('.')[1 : 2], b:grammarous_result)<CR>
try
call operator#user#define('grammarous', 'operator#grammarous#do')
catch /^Vim\%((\a\+)\)\=:E117/
" vim-operator-user is not installed
endtry
let g:loaded_grammarous = 1

View File

@ -0,0 +1,56 @@
from .base import Base
class Source(Base):
def __init__(self, vim):
super().__init__(vim)
self.name = 'grammarous'
self.kind = 'file'
def on_init(self, context):
context['__path'] = self.vim.current.buffer.name
def convert(self, item, context):
"""convert one item from the search result to a candidate"""
word = "'{0}' -> {1}".format(
item['context'][int(item['contextoffset']):(
int(item['contextoffset']) + int(item['errorlength'])
)], item['msg']
)
return {
'word': word,
'action__path': context['__path'],
'action__line': int(item['fromy']) + 1,
'action__col': int(item['fromx']) + 1
}
def gather_candidates(self, context):
try:
result = self.vim.eval('b:grammarous_result')
except Exception as e:
result = []
return [self.convert(item, context) for item in result]
def define_syntax(self):
self.vim.command(
"""syntax match deniteSource_grammarous /\\v^.*$/"""
)
self.vim.command(
"""syntax region deniteSource__GrammarousError start="'" """
"""end="'" oneline contained containedin=deniteSource_grammarous"""
)
self.vim.command(
"""syntax match deniteSource__GrammarousArrow "->" """
"""contained containedin=deniteSource_grammarous"""
)
def highlight(self):
self.vim.command(
'highlight default link deniteSource__GrammarousArrow Keyword'
)
self.vim.command(
'highlight default link deniteSource__GrammarousError ErrorMsg'
)