1
0
mirror of https://github.com/SpaceVim/SpaceVim.git synced 2025-03-13 02:05:40 +08:00

refactor(bundle): use bundle helpful.vim

5bb3e739ff
This commit is contained in:
wsdjeg 2022-01-05 07:26:01 +08:00
parent 524ae813ad
commit e9099b6611
12 changed files with 68931 additions and 1 deletions

View File

@ -86,7 +86,7 @@ function! SpaceVim#layers#lang#vim#plugins() abort
" https://github.com/maralla/completor.vim/issues/250
endif
endif
call add(plugins,['tweekmonster/helpful.vim', {'on_cmd': 'HelpfulVersion'}])
call add(plugins, [g:_spacevim_root_dir . 'bundle/helpful.vim', {'merged' : 0, 'on_cmd' : 'HelpfulVersion'}])
return plugins
endfunction

View File

@ -0,0 +1,29 @@
name: Update Tags
on:
# Run at midnight every day
schedule:
- cron: '0 0 * * *'
jobs:
update:
name: Update Tags
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v2
- name: update tags
run: make update
- name: get date
id: date
run: echo "::set-output name=date::$(date --rfc-3339=seconds)"
- name: check for update
id: diff
run: echo "::set-output name=diff::$(git diff --name-only data/)"
- name: push
uses: actions-x/commit@v2
with:
name: Tag Update Bot
files: data/tags doc/helpful-version.txt
message: Auto-updated tags ${{ steps.date.outputs.date }}
if: ${{ steps.diff.outputs.diff != '' }}

92
bundle/helpful.vim/.gitignore vendored Normal file
View File

@ -0,0 +1,92 @@
/support/src
### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*,cover
.hypothesis/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
# Flask instance folder
instance/
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# IPython Notebook
.ipynb_checkpoints
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# dotenv
.env
# virtualenv
venv/
ENV/
# Spyder project settings
.spyderproject
# Rope project settings
.ropeproject

View File

@ -0,0 +1,20 @@
The MIT License
Copyright (c) 2016 Tommy Allen
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,13 @@
.PHONY: update auto
update:
cd support && python difftags.py
auto:
-test "master" = "$$(git rev-parse --abbrev-ref HEAD)" \
&& python3 support/difftags.py \
&& test " M data/tags" = "$$(git status --porcelain | grep 'data/tags')" \
&& git add data doc \
&& git commit -m "Auto-updated tags $$(date --rfc-3339=seconds)" \
&& git push
git checkout -- doc/

View File

@ -0,0 +1,37 @@
# helpful.vim
A plugin for plugin developers to get the version of Vim and Neovim that
introduced or removed features.
![helpful](https://cloud.githubusercontent.com/assets/111942/16898497/2bf0a402-4baa-11e6-9f9b-3793384d5894.png)
## Usage
The command `:HelpfulVersion` takes a Vim pattern to search for helptags and
display version information.
Examples:
```vim
" Search for a function
:HelpfulVersion matchaddpos()
" Search for keys
:HelpfulVersion <.*>
" Case-insensitive search
:HelpfulVersion f11\c
```
## Options
- `b:helpful` - If set to `1`, display version information about the text under
the cursor on `CursorMoved` in `help` or `vim` filetypes.
- `g:helpful` - Same as above but always on. It's also less humorous to read
out loud.
## License
MIT

View File

@ -0,0 +1,389 @@
let s:base = expand('<sfile>:p:h:h')
" Text around the word that might produce a match, in order of 'usefulness' to
" a developer.
let s:ornaments = [
\ [':', ''],
\ ['', '()'],
\ [':func-', ''],
\ ["'", "'"],
\ ['<', '>'],
\ ['@', ''],
\ ['"', ''],
\ ['hl-', ''],
\ ['syn-', ''],
\ ['[', ']'],
\ ['{', '}'],
\ ['+', ''],
\ ]
function! s:load_data() abort
if exists('s:data')
return
endif
let s:data = {}
for line in readfile(s:base.'/data/tags')
let parts = split(line, "\x07")
for flavor in parts[1:]
let name = matchstr(flavor, '^[^:]\+')
let versions = split(matchstr(flavor, ':\zs.*'), ',', 1)
if !has_key(s:data, parts[0])
let s:data[parts[0]] = {}
endif
let s:data[parts[0]][name] = {
\ '+': versions[0],
\ '-': versions[1],
\ }
endfor
endfor
endfunction
" Wrap a pattern with an outer pattern that matches likely helptag characters.
function! s:pattern_wrap(pattern, help) abort
let pattern = a:pattern
if a:help
let tagpattern = '\%(\1\@!.\)*'.pattern.'\%(\1\@!.\)*'
return '\%(\([|*]\)\zs'.tagpattern.'\ze\1\)\|'
\ .'\%(\zs''[^'']*'.pattern.'[^'']*''\ze\)\|'
\ .'\%(\zs\%(:\%(\w\|[-_]\)\)\?<[^<>]*'.pattern.'[^<>]*>\ze\)\|'
\ .'\%(\zs\[[^\[\]]*'.pattern.'[^\[\]]*\]\ze\)\|'
\ .'\%(\zs{[^{}]*'.pattern.'[^{}]*}\ze\)\|'
\ .'\zs\%(\k\|[_-]\)*'.pattern.'\%(\k\|[_-]\)*\ze('
endif
let word_atom = '\%(\w\|[&:_@#{}/\+-]\)*'
return word_atom.pattern.word_atom
endfunction
" Get the word under the cursor.
" <cword> and <cWORD> aren't reliable for helpful.vim
function! s:cword() abort
let pattern = s:pattern_wrap('\%'.col('.').'c', &filetype == 'help')
let word = matchstr(getline('.'), pattern)
if &filetype == 'help'
return word
endif
if word =~# '^!'
let word = word[1:]
endif
if empty(word)
return ''
endif
" Force a match on <Key> tags, but make an exception for :map-<whatever>
if word !~# '^:' && word =~# '<.*>'
let word = matchstr(word, '<[^>]\+>')
endif
" Find some clues about the text under the cursor.
if word =~# '^&l:'
let word = "'".word[3:]."'"
elseif word =~# '^&'
let word = "'".word[1:]."'"
elseif word =~# '^end.'
let word = word[3:]
endif
return word
endfunction
" Find the best match for a helptag. Oranments are put around the the tag if
" there's no first match. If there is no match, return an empty string.
function! s:match_word(word) abort
call s:load_data()
if has_key(s:data, a:word)
return a:word
endif
for [t1, t2] in s:ornaments
if has_key(s:data, t1.a:word.t2)
return t1.a:word.t2
endif
endfor
return ''
endfunction
function! s:helptag_version(word, ...) abort
let word = a:word
if !exists('b:_helpful_word') || b:_helpful_word[0] != word
let word = s:match_word(word)
if empty(word)
return
endif
let info = s:data[word]
let b:_helpful_word = [a:word, word, info]
else
let word = b:_helpful_word[1]
let info = b:_helpful_word[2]
endif
let keys = has('nvim') ? ['neovim', 'vim'] : ['vim', 'neovim']
if !a:0
redraw
endif
echohl Special
echo printf('%*S', a:0 ? a:1 : 0, word)
echohl None
for k in keys
if !has_key(info, k) || (empty(info[k]['+']) && empty(info[k]['-']))
continue
endif
echon ' | '
echon k.': '
if !empty(info[k]['+'])
echohl DiffAdd
echon '+'.info[k]['+']
echohl None
endif
if !empty(info[k]['-'])
if !empty(info[k]['+'])
echon ', '
endif
echohl DiffDelete
echon '-'.info[k]['-']
echohl None
endif
endfor
endfunction
" Find a helptag using a word under the cursor
function! helpful#cursor_word() abort
if mode() ==? 'v'
" Need to save the original visual marks? Visual marks aren't updated
" until visual mode is left.
let view = winsaveview()
let v1 = getpos("'<")
let v2 = getpos("'>")
execute "normal! \<esc>gv"
let p1 = getpos("'<")[1:2]
let p2 = getpos("'>")[1:2]
call winrestview(view)
call setpos("'<", v1)
call setpos("'>", v2)
if p1[0] != p2[0]
return
endif
let word = getline(p1[0])[p1[1]-1:p2[1]-1]
else
let word = s:cword()
endif
if empty(word)
echo ''
return
endif
call s:helptag_version(word)
endfunction
" Normalize Vim's goofy old versions so they can be compared with the other
" versions.
"
" Where M = major, m = minor, R = rev, A = alpha
" MmRRRRA
"
" Vim:
" 7.4.300 = 7403000
" 7.2.1234 = 7212340
" 7.1a = 7100001
"
" Neovim:
" 0.1.3 = 100030
" 1.0.1 = 1000010
" 2.5.22 = 2500220
function! s:parse_version(version) abort
let vparts = split(matchstr(a:version, '\d.*'), '\.')
if empty(vparts)
return 0
endif
let major = str2nr(vparts[0])
let minor = 0
let alpha = 0
let rev = 0
if vparts[1] =~ '\D'
let minor = str2nr(matchstr(vparts[1], '\d'))
let alpha = char2nr(tolower(matchstr(vparts[1], '\D'))) - 96
elseif len(vparts) == 2 && vparts[1] =~# '\d\{4}'
let minor = 0
let rev = str2nr(vparts[1])
else
let minor = str2nr(vparts[1])
endif
if len(vparts) > 2
let rev = str2nr(vparts[2])
endif
return major * 1000000 +
\ minor * 100000 +
\ rev * 10 +
\ alpha
endfunction
" Reverses the result of s:parse_version()
function! s:unparse_version(version) abort
if !a:version || a:version == 99999999
return '???'
endif
let v = a:version
let major = a:version / 1000000
let v -= major * 1000000
let minor = v / 100000
let v -= minor * 100000
let rev = v / 10
let v -= rev * 10
if v
return printf('v%d.%d%s', major, minor, nr2char(v + 96))
elseif major == 7 && minor == 0
return printf('v%d.0%03d', major, rev)
endif
return printf('v%d.%d.%0*d', major, minor, major >= 7 ? 3 : 0, rev)
endfunction
function! s:_lookup_sort(a, b) abort
if a:a[1] < a:b[1]
return -1
elseif a:a[1] > a:b[1]
return 1
endif
let al = strlen(a:a[0])
let bl = strlen(a:b[0])
if al < bl
return -1
elseif al > bl
return 1
endif
return 0
endfunction
" Find helptag using a pattern and print the results.
function! helpful#lookup(pattern) abort
call s:load_data()
let tags = []
let width = 0
for tag in keys(s:data)
let m = match(tag, a:pattern)
if m != -1
call add(tags, [tag, m])
let width = max([width, strlen(tag)])
endif
endfor
let s:search_pattern = a:pattern
for [tag, _] in sort(tags, 's:_lookup_sort')
call s:helptag_version(tag, width)
endfor
endfunction
function! s:min_pair(x, y) abort
return a:x[0] < a:y[0] ? a:x : a:y
endfunction
function! s:max_pair(x, y) abort
return a:x[0] > a:y[0] ? a:x : a:y
endfunction
" Find functions in the buffer and print min and max versions that are
" required. Only a proof of concept, there is no plan for it to be smarter.
function! helpful#buffer_version() abort
call s:load_data()
let view = winsaveview()
let s = @/
let b = getreg('b')
let bt = getregtype('b')
normal! qbq
silent g/\k\+(/normal! gn"By
let @/ = s
call histdel('search', -1)
let funcs = map(split(getreg('b'), '('), 'v:val."()"')
call setreg('b', b, bt)
call winrestview(view)
let neovim_min = [0, '']
let neovim_max = [99999999, '']
let vim_min = [0, '']
let vim_max = [99999999, '']
for f in funcs
if has_key(s:data, f)
let vinfo = s:data[f]
if has_key(vinfo, 'neovim')
let neovim_max = s:min_pair(neovim_max,
\ [s:parse_version(vinfo['neovim']['-']), f])
let neovim_min = s:max_pair(neovim_min,
\ [s:parse_version(vinfo['neovim']['+']), f])
endif
if has_key(vinfo, 'vim')
let vim_max = s:min_pair(vim_max,
\ [s:parse_version(vinfo['vim']['-']), f])
let vim_min = s:max_pair(vim_min,
\ [s:parse_version(vinfo['vim']['+']), f])
endif
endif
endfor
echo printf('Neovim: %s (%s) - %s (%s)',
\ s:unparse_version(neovim_min[0]), neovim_min[1],
\ s:unparse_version(neovim_max[0]), neovim_max[1])
echo printf('Vim: %s (%s) - %s (%s)',
\ s:unparse_version(vim_min[0]), vim_min[1],
\ s:unparse_version(vim_max[0]), vim_max[1])
endfunction
function! s:enabled() abort
return get(b:, 'helpful', get(g:, 'helpful', 0))
endfunction
function! helpful#setup() abort
augroup helpful
autocmd! * <buffer>
autocmd CursorMoved <buffer> if s:enabled() | call helpful#cursor_word() | endif
augroup END
endfunction

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,5 @@
augroup help_versions
autocmd! FileType vim,help call helpful#setup()
augroup END
command! -nargs=+ -complete=help HelpfulVersion call helpful#lookup(<q-args>)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,4 @@
# Attention!
You do not need to run `difftags.py` yourself. It is a utility for keeping
this repo up to date.

View File

@ -0,0 +1,200 @@
import os
import re
import sys
import subprocess
from datetime import date, datetime
from collections import defaultdict
base = os.path.dirname(__file__)
src = os.path.abspath(os.path.join(base, 'src'))
repos = {
'neovim': 'https://github.com/neovim/neovim.git',
'vim': 'https://github.com/vim/vim.git',
}
neovim_repo = os.path.join(src, 'neovim')
vim_repo = os.path.join(src, 'vim')
_tag_re = re.compile(br'\s\*([^*\s]+)\*\s')
if not os.path.isdir(src):
os.makedirs(os.path.join(src))
def command(cmd, print_stdout=False, print_stderr=True, cwd=None):
"""Run a git command and return the stdout as lines.
The stdout is returned as bytes in case a diff breaks up unicode bytes.
"""
p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, cwd=cwd)
stdout, stderr = p.communicate()
if print_stdout and stdout:
print(stdout.decode('utf8'))
if print_stderr and stderr:
print(cmd, stderr.decode('utf8'), file=sys.stderr)
return list(filter(None, stdout.split(b'\n')))
def get_latest(repo):
path = os.path.join(src, repo)
if os.path.exists(path):
return command(['git', 'fetch', '-q', '--tags', repos.get(repo),
'master:master'], True, cwd=path)
else:
return command(['git', 'clone', '-q', '--bare', '--single-branch',
repos.get(repo), path], True)
def normalize_hunk(lines):
"""Normalize a hunk to give ample whitespace between tag delimiters.
Also cleans out garbage whitespace
"""
out = b' '
for line in lines:
out += re.sub(br'\s', b' ', line[1:]).replace(b'* *', b'* *') + b' '
return out
def doc_diff(path, a, b):
"""Parse diff lines to get tags
A counter is used to keep track of the additions and deletions.
"""
tags = defaultdict(int)
if not a or not b:
cmd = ['git', 'show', '-U0', '--oneline', a or b, '--',
'runtime/doc/*.txt']
else:
cmd = ['git', 'diff', '-U0', a, b, '--', 'runtime/doc/*.txt']
stdout = command(cmd, cwd=path)
i = 0
while i < len(stdout):
line = stdout[i]
i += 1
if line[:2] != b'@@':
continue
delta = line.split()
deleted = 1
added = 1
if b',' in delta[1]:
deleted = int(delta[1].split(b',')[-1])
if b',' in delta[2]:
added = int(delta[2].split(b',')[-1])
if deleted > 0:
for t in _tag_re.findall(normalize_hunk(stdout[i:i+deleted])):
tags[t] -= 1
i += deleted
if added > 0:
for t in _tag_re.findall(normalize_hunk(stdout[i:i+added])):
tags[t] += 1
i += added
return tags
def repo_tags(path):
"""Get tags that can be diff'd."""
stdout = command(['git', 'log', '--pretty=oneline'], cwd=path)
first = stdout[-1].decode('utf8').split()[0]
last = stdout[0].decode('utf8').split()[0]
cmd = ['git', 'for-each-ref',
"--format=%(*committerdate:raw)%(committerdate:raw) %(refname) %(objectname)",
'refs/tags']
tags = [('alpha', first, None)]
stdout = command(cmd, print_stderr=False, cwd=path)
for line in sorted(stdout, key=lambda x: x.split()[0]):
parts = line.decode('utf8').split()
timestamp, tz = parts[:2]
tag_date = date.fromtimestamp(int(timestamp))
tag, ref = parts[-2:]
tag = tag.split('/')[-1]
if tag.startswith('untagged-'):
continue
tags.append((tag, ref, tag_date))
tags.append(('dev', last, None))
tags.append(('dev', 'HEAD', None))
return tags
def collect(repo):
"""Collects the helptag differences between versions."""
path = os.path.join(src, repo)
tags = repo_tags(path)
versions = defaultdict(lambda: {'added': [], 'removed': []})
for i in range(len(tags) - 1):
helptags = doc_diff(path, tags[i][1], tags[i+1][1])
version = versions[tags[i+1][0]]
for k, v in helptags.items():
if v < 0:
version['removed'].append(k)
elif v > 0:
version['added'].append(k)
return [(info, versions[info[0]]) for info in tags]
def generate():
"""Generate the data file for the plugin.
All byte strings from here.
"""
get_latest('neovim')
get_latest('vim')
tags = defaultdict(lambda: defaultdict(lambda: defaultdict(bytes)))
for target in (b'neovim', b'vim'):
for (version, ref, tag_date), deltas in collect(target.decode('ascii')):
for change, items in deltas.items():
for item in items:
tags[item][target][change] = version.encode('ascii')
with open(os.path.join(base, '..', 'data', 'tags'), 'wb') as fp:
with open(os.path.join(base, '..', 'doc',
'helpful-version.txt'), 'wb') as hfp:
hfp.write(b'*helpful-version.txt* Generated: ' +
str(datetime.utcnow().replace(microsecond=0))
.encode('utf8') + b' UTC')
hfp.write(b'\n\n')
hfp.write(b'A listing of helptags with the versions they became '
b'available or were removed.\n\n')
for helptag, targets in sorted(tags.items(), key=lambda x: x[0]):
# Not creating new tags since it adds too much noise to help
# completion.
hfp.write(b'\n|' + helptag + b'|\n\n')
target_versions = []
for target, versions in sorted(targets.items(), key=lambda x: x[0]):
hfp.write(b' ' + target.title().rjust(8) + b': ')
if versions['added']:
hfp.write(b'+' + versions['added'] + b' ')
if versions['removed']:
hfp.write(b'-' + versions['removed'] + b' ')
hfp.write(b'\n')
target_versions.append(target + b':' +
versions['added'] + b',' +
versions['removed'])
fp.write(b'\x07'.join([helptag] + target_versions))
fp.write(b'\n')
if __name__ == '__main__':
generate()