├── .gitignore ├── README.md ├── autoload └── jira.vim ├── jira-complete-demo.gif ├── plugin └── vim-jira-complete.vim ├── py └── vimjira.py └── vim-jira-complete-addon-info.txt /.gitignore: -------------------------------------------------------------------------------- 1 | .*.swp 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vim-jira-complete 2 | 3 | `jira-complete` is a Vim plugin that queries JIRA issues and make a AutoComplete list for you. 4 | 5 | This will be helpful if you are using Jira with DVCS smart commits and [Fugitive.vim](https://github.com/tpope/vim-fugitive) 6 | 7 | ## Demo 8 | 9 | ![demo](jira-complete-demo.gif) 10 | 11 | ## How to use 12 | 13 | \ in insert mode. 14 | 15 | This binding can be configured in your .vimrc with for instance: 16 | 17 | ``` 18 | imap j JiraComplete 19 | ``` 20 | 21 | ## Installation 22 | 23 | ##### Vim-Plug (https://github.com/junegunn/vim-plug) 24 | 25 | ``` 26 | Plug 'mnpk/vim-jira-complete' 27 | ``` 28 | 29 | ##### Vundle (https://github.com/gmarik/Vundle.vim) 30 | 31 | ``` 32 | Plugin 'mnpk/vim-jira-complete' 33 | ``` 34 | 35 | ##### VAM 36 | 37 | ``` 38 | " For the official version 39 | InstallAddon mnpk/vim-jira-complete 40 | " For Luc Hermitte's fork 41 | InstallAddon LucHermitte/vim-jira-complete 42 | ``` 43 | 44 | ##### Manual Install 45 | 46 | ``` 47 | cd ~/.vim/bundle 48 | git clone git://github.com/mnpk/vim-jira-complete.git 49 | ``` 50 | 51 | ## Dependency 52 | 53 | Python support and [requests](http://docs.python-requests.org/) package. 54 | 55 | ``` 56 | pip install requests 57 | ``` 58 | 59 | ## Settings 60 | 61 | ### Credentials 62 | The settings can be global, for instance, write in your `.vimrc`: 63 | 64 | ``` 65 | let g:jiracomplete_url = 'http://your.jira.url/' 66 | let g:jiracomplete_username = 'your_jira_username' 67 | let g:jiracomplete_password = 'your_jira_password' " optional 68 | ``` 69 | 70 | or settings can be [local to a project](https://github.com/LucHermitte/local_vimrc) 71 | (for those who work on several projects, each managed with a different JIRA): 72 | 73 | ``` 74 | let b:jiracomplete_url = 'http://your.jira.url/' 75 | let b:jiracomplete_username = 'your_jira_username' 76 | let b:jiracomplete_password = 'your_jira_password' " optional 77 | ``` 78 | 79 | ### Format of the string inserted 80 | 81 | By default, upon \, the completion menu will insert the ID of the issue 82 | selected. If you also want the string, you can change the format with the 83 | option `[bg]:jiracomplete_format`: 84 | 85 | ``` 86 | let g:jiracomplete_format = 'v:val.abbr . " -> " . v:val.menu' 87 | ``` 88 | 89 | for `KEY-123 -> Your Issue Title` 90 | 91 | ``` 92 | let g:jiracomplete_format = '"[". v:val.abbr . "]"' 93 | ``` 94 | for `[KEY-123]` 95 | 96 | 97 | ### Certificates 98 | If the JIRA site is using a certificate that is not installed on your local 99 | machine, you will see this kind of error message: 100 | 101 | ``` 102 | requests.exceptions.SSLError: [Errno 185090050] _ssl.c:328: error:0B084002:x509 certificate routines:X509_load_cert_crl_file:system lib 103 | ``` 104 | 105 | In that case, you'll have to install a certificate that _requests_ library will 106 | be able to use. In you cannot install any, you'll have to parametrize the JIRA 107 | URL to say: _Please don't try to verify anything_ this way: 108 | 109 | ``` 110 | let g:jiracomplete_url = {'url': 'http://your.jira.url/', 'verify': 'False' } 111 | ``` 112 | 113 | In other words, the URL can be a dictionary of options that'll be passed to 114 | _requests_ ` get()` function. Consult the relevant documentation for more 115 | information on what you could do with it. 116 | 117 | ## Credits 118 | vim-jira-complete has been initiated by mnpk. 119 | 120 | Luc Hermitte provided some enhancements. 121 | 122 | Using Luc Hermitte's Vim library [lh-vim-lib](http://code.google.com/p/lh-vim/wiki/lhVimLib). 123 | -------------------------------------------------------------------------------- /autoload/jira.vim: -------------------------------------------------------------------------------- 1 | "============================================================================= 2 | " $Id$ 3 | " File: vim-jira-complete/autoload/jira.vim {{{1 4 | " Authors: 5 | " mnpk , initial author of the plugin, 2014 6 | " Luc Hermitte, enhancements to the plugin, 2014 7 | " jira#lh_... functions are copied from Luc Hermitte's vim library [lh-vim-lib](http://code.google.com/p/lh-vim/wiki/lhVimLib). 8 | " Version: 0.2.0 9 | let s:k_version = 020 10 | "------------------------------------------------------------------------ 11 | " Description: 12 | " Internals and API functions for vim-jira-complete 13 | " }}}1 14 | "============================================================================= 15 | 16 | let s:cpo_save=&cpo 17 | set cpo&vim 18 | "------------------------------------------------------------------------ 19 | " ## Misc Functions {{{1 20 | " # Version {{{2 21 | function! jira#version() 22 | return s:k_version 23 | endfunction 24 | 25 | " # Debug {{{2 26 | if !exists('s:verbose') 27 | let s:verbose = 0 28 | endif 29 | function! jira#verbose(...) 30 | if a:0 > 0 | let s:verbose = a:1 | endif 31 | return s:verbose 32 | endfunction 33 | 34 | function! s:Verbose(expr) 35 | if s:verbose 36 | echomsg a:expr 37 | endif 38 | endfunction 39 | 40 | function! jira#debug(expr) 41 | return eval(a:expr) 42 | endfunction 43 | 44 | 45 | "------------------------------------------------------------------------ 46 | " ## lh functions {{{1 47 | " Function: jira#lh_option_get(name, default [, scope]) {{{2 48 | " @return b:{name} if it exists, or g:{name} if it exists, or {default} 49 | " otherwise 50 | " The order of the variables checked can be specified through the optional 51 | " argument {scope} 52 | function! jira#lh_option_get(name,default,...) 53 | let scope = (a:0 == 1) ? a:1 : 'bg' 54 | let name = a:name 55 | let i = 0 56 | while i != strlen(scope) 57 | if exists(scope[i].':'.name) 58 | " \ && (0 != strlen({scope[i]}:{name})) 59 | " This syntax doesn't work with dictionaries -> !exe 60 | " return {scope[i]}:{name} 61 | exe 'return '.scope[i].':'.name 62 | endif 63 | let i += 1 64 | endwhile 65 | return a:default 66 | endfunction 67 | 68 | " Function: jira#lh_common_warning_msg {{{2 69 | function! jira#lh_common_warning_msg(text) 70 | echohl WarningMsg 71 | " echomsg a:text 72 | call jira#lh_common_echomsg_multilines(a:text) 73 | echohl None 74 | endfunction 75 | 76 | " Function: jira#lh_common_echomsg_multilines {{{2 77 | function! jira#lh_common_echomsg_multilines(text) 78 | let lines = type(a:text) == type([]) ? a:text : split(a:text, "[\n\r]") 79 | for line in lines 80 | echomsg line 81 | endfor 82 | endfunction 83 | 84 | function! jira#lh_get_current_keyword() 85 | let c = col ('.')-1 86 | let l = line('.') 87 | let ll = getline(l) 88 | let ll1 = strpart(ll,0,c) 89 | let ll1 = matchstr(ll1,'\k*$') 90 | if strlen(ll1) == 0 91 | return ll1 92 | else 93 | let ll2 = strpart(ll,c,strlen(ll)-c+1) 94 | let ll2 = matchstr(ll2,'^\k*') 95 | " let ll2 = strpart(ll2,0,match(ll2,'$\|\s')) 96 | return ll1.ll2 97 | endif 98 | endfunction 99 | "------------------------------------------------------------------------ 100 | " ## Exported functions {{{1 101 | " # Issues lists {{{2 102 | " Function: jira#_do_fetch_issues() {{{3 103 | function! jira#_do_fetch_issues() abort 104 | if s:py_script_timestamp == 0 105 | call jira#_init_python() 106 | endif 107 | let url = jira#lh_option_get('jiracomplete_url', '') 108 | if len(url) == 0 109 | throw "Error: [bg]:jiracomplete_url is not specified" 110 | endif 111 | let username = jira#lh_option_get('jiracomplete_username', '') 112 | if len(username) == 0 113 | throw "Error: [bg]:jiracomplete_username is not specified" 114 | endif 115 | let password = jira#_get_password(username) 116 | py vim.command('let issues=['+jira_complete(vim.eval('url'), vim.eval('username'), vim.eval('password'))+']') 117 | if len(issues) == 1 && type(issues[0])==type('') 118 | throw issues 119 | else 120 | return issues 121 | endif 122 | endfunction 123 | 124 | " Function: jira#get_issues() {{{3 125 | " First from the cache, unless the cache is empty 126 | if !exists('s:cached_issues') 127 | let s:cached_issues = [] 128 | endif 129 | 130 | function! jira#get_issues(force_update) abort 131 | if empty(s:cached_issues) || a:force_update 132 | let s:cached_issues = jira#_do_fetch_issues() 133 | endif 134 | return s:cached_issues 135 | endfunction 136 | 137 | " # Completion {{{2 138 | " Function: jira#_complete([force_update_cache]) {{{3 139 | function! jira#_complete(...) abort 140 | let issues = jira#get_issues(a:0 ? a:1 : 0) 141 | " Hint: let g:jiracomplete_format = 'v:val.abbr . " -> " . v:val.menu' 142 | let format = jira#lh_option_get('jiracomplete_format', "v:val.abbr") 143 | call map(issues, "extend(v:val, {'word': ".format.'})') 144 | let lead = jira#lh_get_current_keyword() " From lh-vim-lib 145 | call filter(issues, 'v:val.abbr =~ lead') 146 | if !empty(issues) 147 | call complete(col('.')-len(lead), issues) 148 | else 149 | call jira#lh_common_warning_msg("No Issue ID starting with ".lead) 150 | endif 151 | return '' 152 | endfunction 153 | 154 | "------------------------------------------------------------------------ 155 | " ## Internal functions {{{1 156 | 157 | " # Python module init {{{2 158 | " Function: jira#_init_python() {{{3 159 | " The Python module will be loaded only if it has changed since the last time 160 | " this autoload plugin has been sourced. It is of course loaded on the first 161 | " time. Note: this feature is to help maintain the plugin. 162 | let s:py_script_timestamp = 0 163 | let s:plugin_root_path = expand(':p:h:h') 164 | let s:jirapy_script = s:plugin_root_path . '/py/vimjira.py' 165 | function! jira#_init_python() abort 166 | if !filereadable(s:jirapy_script) 167 | throw "Cannot find vim-jira python script: ".s:jirapy_script 168 | endif 169 | let ts = getftime(s:jirapy_script) 170 | if s:py_script_timestamp >= ts 171 | return 172 | endif 173 | " jira_complete python part is expected to be already initialized 174 | call jira#verbose("Importing ".s:jirapy_script) 175 | python import sys 176 | exe 'python sys.path = ["' . s:plugin_root_path . '"] + sys.path' 177 | exe 'pyfile ' . s:jirapy_script 178 | let s:py_script_timestamp = ts 179 | endfunction 180 | 181 | " # Options related functions {{{2 182 | " Function: jira#_get_password() {{{3 183 | function! jira#_get_password(username) 184 | let password = jira#lh_option_get('jiracomplete_password', '') 185 | if len(password) == 0 186 | call inputsave() 187 | let password = inputsecret('Please input jira password for '.a:username.': ') 188 | " The password is voluntarilly not cached in case the end user wants to 189 | " keep its privacy 190 | call inputrestore() 191 | echohl None 192 | endif 193 | return password 194 | endfunction 195 | 196 | "------------------------------------------------------------------------ 197 | " ## Initialize module {{{1 198 | call jira#_init_python() 199 | "------------------------------------------------------------------------ 200 | " }}}1 201 | let &cpo=s:cpo_save 202 | "============================================================================= 203 | " vim600: set fdm=marker: 204 | -------------------------------------------------------------------------------- /jira-complete-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mnpk/vim-jira-complete/832aa3f11e704c656e2d32b6dd792167e6cebe68/jira-complete-demo.gif -------------------------------------------------------------------------------- /plugin/vim-jira-complete.vim: -------------------------------------------------------------------------------- 1 | "============================================================================= 2 | " $Id$ 3 | " File: plugin/vim-jira-complete.vim {{{1 4 | " Authors: 5 | " mnpk , initial author of the plugin, 2014 6 | " Luc Hermitte, enhancements to the plugin, 2014 7 | " Version: 0.2.0 8 | let s:k_version = 020 9 | "------------------------------------------------------------------------ 10 | " Description: 11 | " Autocomplete plugin from Jira issues. 12 | " }}}1 13 | "============================================================================= 14 | 15 | if !has('python') 16 | echo "Error: Required vim compiled with +python" 17 | finish 18 | endif 19 | 20 | " Avoid global reinclusion {{{1 21 | if &cp || (exists("g:loaded_vim_jira_complete") 22 | \ && (g:loaded_vim_jira_complete >= s:k_version) 23 | \ && !exists('g:force_reload_vim_jira_complete')) 24 | finish 25 | endif 26 | let g:loaded_vim_jira_complete = s:k_version 27 | let s:cpo_save=&cpo 28 | set cpo&vim 29 | " Avoid global reinclusion }}}1 30 | "------------------------------------------------------------------------ 31 | " Commands and Mappings {{{1 32 | 33 | inoremap JiraCompleteIgnoreCache =jira#_complete(1) 34 | inoremap JiraComplete =jira#_complete(0) 35 | if !hasmapto('JiraComplete', 'i') 36 | imap JiraComplete 37 | endif 38 | 39 | command! -nargs=0 JiraCompleteUpdateCache call jira#get_issues(1) 40 | " Commands and Mappings }}}1 41 | "------------------------------------------------------------------------ 42 | let &cpo=s:cpo_save 43 | "============================================================================= 44 | " vim600: set fdm=marker: 45 | 46 | -------------------------------------------------------------------------------- /py/vimjira.py: -------------------------------------------------------------------------------- 1 | # File: py/vimjira.py {{{1 2 | # Authors: 3 | # mnpk , initial author of the plugin, 2014 4 | # Luc Hermitte, enhancements to the plugin, 2014 5 | # Version: 0.2.0 6 | # Description: 7 | # Internals and API functions for vim-jira-complete 8 | # }}}1 9 | #====================================================================== 10 | import vim 11 | import json 12 | import requests 13 | import base64 14 | 15 | def get_password_for(user): 16 | return vim.eval('jira#_get_password("'+user+'")') 17 | 18 | def jira_complete(url, user, pw, need_retry=True): 19 | # print "URL: ", url 20 | # print "user: ", user 21 | # print "pw: ", pw 22 | headers = {} 23 | if pw: 24 | auth = base64.b64encode(user+':'+pw) 25 | headers['authorization'] = 'Basic ' + auth 26 | query = "jql=assignee=%s+and+resolution=unresolved" % user 27 | if type(url) == type(dict()): 28 | raw_url = url['url'] 29 | api_url = "%s/rest/api/2/search?%s" % (raw_url, query) 30 | args = url.copy() 31 | args.pop('url') 32 | for k in args.keys(): 33 | if args[k] == 'False' or args[k] == 'True': 34 | args[k] = eval(args[k]) 35 | response = requests.get(api_url, headers=headers, **args) 36 | else: 37 | api_url = "%s/rest/api/2/search?%s" % (url, query) 38 | response = requests.get(api_url, headers=headers) 39 | 40 | print "api_url: ", api_url 41 | print "headers: ", headers 42 | if response.status_code == requests.codes.ok: 43 | jvalue = json.loads(response.content) 44 | issues = jvalue['issues'] 45 | match = [] 46 | for issue in issues: 47 | match.append("{\"abbr\": \"%s\", \"menu\": \"%s\"}" % 48 | (issue['key'], issue['fields']['summary'].replace("\"", "\\\""))) 49 | return ','.join(match) 50 | elif (response.status_code == requests.codes.unauthorized or 51 | response.status_code == requests.codes.bad_request or 52 | response.status_code == requests.codes.forbidden): 53 | if need_retry: 54 | pw = get_password_for(user) 55 | jira_complete(url, user, pw, need_retry=False) 56 | else: 57 | return "Error: " + response.reason 58 | else: 59 | return "Error: " + response.reason 60 | 61 | -------------------------------------------------------------------------------- /vim-jira-complete-addon-info.txt: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vim-jira-complete", 3 | "version": "dev", 4 | "author" : "mnpk, Luc Hermitte", 5 | "maintainer" : "mnpk", 6 | "repository" : {"type": "git", "url": "git@github.com:LucHermitte/vim-jira-complete"}, 7 | "dependencies" : { 8 | "lh-vim-lib" : {"type" : "svn", "url" : "http://lh-vim.googlecode.com/svn/vim-lib/trunk"} 9 | }, 10 | "description" : "Vim plugin that query JIRA Issues and make autocomplete" 11 | } 12 | --------------------------------------------------------------------------------