├── README.md ├── .gitignore └── rplugin └── python3 └── deoplete └── sources └── github.py /README.md: -------------------------------------------------------------------------------- 1 | # Deoplete Github 2 | 3 | ## Dependencies 4 | 5 | I've tried not to replicate existing behaviour from already great plugins: 6 | 7 | - [deoplete][] 8 | - [fugitive][] 9 | - [rhubarb][] 10 | 11 | ## Install 12 | 13 | Install with your favourite vim plugin manager: 14 | 15 | ```vim 16 | Plug 'SevereOverfl0w/deoplete-github' 17 | ``` 18 | 19 | Add it as a deoplete source: 20 | 21 | ```vim 22 | let g:deoplete#sources = {} 23 | let g:deoplete#sources.gitcommit=['github'] 24 | ``` 25 | 26 | Deoplete also needs a little configuration: 27 | 28 | ```vim 29 | let g:deoplete#keyword_patterns = {} 30 | let g:deoplete#keyword_patterns.gitcommit = '.+' 31 | 32 | call deoplete#util#set_pattern( 33 | \ g:deoplete#omni#input_patterns, 34 | \ 'gitcommit', [g:deoplete#keyword_patterns.gitcommit]) 35 | 36 | ``` 37 | 38 | # Usage 39 | 40 | In your git commit message: 41 | 42 | - The prefix "#" will find issue numbers for auto-completion. 43 | - You can also auto-complete github issue titles. 44 | 45 | [fugitive]: https://github.com/tpope/vim-fugitive 46 | [rhubarb]: https://github.com/tpope/vim-rhubarb 47 | [deoplete]: https://github.com/Shougo/deoplete.nvim 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask instance folder 57 | instance/ 58 | 59 | # Scrapy stuff: 60 | .scrapy 61 | 62 | # Sphinx documentation 63 | docs/_build/ 64 | 65 | # PyBuilder 66 | target/ 67 | 68 | # IPython Notebook 69 | .ipynb_checkpoints 70 | 71 | # pyenv 72 | .python-version 73 | 74 | # celery beat schedule file 75 | celerybeat-schedule 76 | 77 | # dotenv 78 | .env 79 | 80 | # virtualenv 81 | venv/ 82 | ENV/ 83 | 84 | # Spyder project settings 85 | .spyderproject 86 | -------------------------------------------------------------------------------- /rplugin/python3/deoplete/sources/github.py: -------------------------------------------------------------------------------- 1 | from .base import Base 2 | import netrc 3 | import re 4 | from urllib.parse import urlparse 5 | import urllib.request as request 6 | import json 7 | import base64 8 | 9 | def repo_homepage(vim): 10 | """Return the repo homepage, akin to rhubarb#repo_request 11 | function 12 | 13 | :returns: String like "https://github.com/user/repo" 14 | """ 15 | 16 | repo_url = vim.eval('fugitive#repo().config("remote.origin.url")') 17 | homepage = vim.call('rhubarb#homepage_for_url', repo_url) 18 | return homepage 19 | 20 | def repo_base(vim): 21 | """ 22 | :vim: Vim Object 23 | :returns: API endpoint for current repo 24 | """ 25 | base = repo_homepage(vim) 26 | if base: 27 | if re.search('//github\.com/', base) is not None: 28 | base = base.replace('//github.com/', '//api.github.com/repos/') 29 | else: 30 | # I'm not sure how to work this 31 | # It's enterprise github, I don't understand vim regex 32 | base = "failure" 33 | pass 34 | 35 | return base 36 | 37 | def authenticator(hostname): 38 | """Parse netrc file into a dict 39 | 40 | :hostname: Hostname to get authenticator for 41 | :returns: Dict with login, account and password key 42 | 43 | """ 44 | myrc = netrc.netrc() 45 | authenticator = myrc.authenticators(hostname) 46 | 47 | return {'login': authenticator[0], 48 | 'account': authenticator[1], 49 | 'password': authenticator[2]} 50 | 51 | class Source(Base): 52 | 53 | """Fetches issues from Github API.""" 54 | 55 | def __init__(self, vim): 56 | Base.__init__(self, vim) 57 | 58 | self.name = 'github' 59 | self.mark = '[GH]' 60 | self.filetypes = ['gitcommit'] 61 | self.input_pattern = '#' 62 | 63 | def gather_candidates(self, context): 64 | """Gather candidates from github API 65 | """ 66 | 67 | base = repo_base(self.vim) 68 | 69 | if base: 70 | base = base + '/issues?per_page=200' 71 | 72 | base_url = urlparse(base) 73 | credentials = authenticator(base_url.hostname) 74 | 75 | r = request.Request(base) 76 | creds = base64.encodestring(bytes('%s:%s' % (credentials.get('login'), credentials.get('password')), 'utf-8')).strip() 77 | r.add_header('Authorization', 'Basic %s' % creds.decode('utf-8')) 78 | 79 | with request.urlopen(r) as req: 80 | response_json = req.read().decode('utf-8') 81 | response = json.loads(response_json) 82 | 83 | titles = [x.get('title', '') for x in response] 84 | numbers = [{'word': '#' + str(x.get('number', '')), 85 | 'menu': x.get('title'), 86 | 'info': x.get('body')} 87 | for x in response] 88 | titles = [{'word': x.get('title'), 89 | 'menu': '#' + str(x.get('number', '')), 90 | 'info': x.get('body')} 91 | for x in response] 92 | return numbers + titles 93 | return [] 94 | --------------------------------------------------------------------------------