├── README ├── README.ja └── plugin └── redmine.vim /README: -------------------------------------------------------------------------------- 1 | Introduction 2 | ============= 3 | 4 | You can look over the information on redmine with vim. 5 | 6 | 7 | Function 8 | ============= 9 | 10 | 1. Display ticket and display outline of ticket on Redmine with vim. 11 | 2. It is possible to jump to URL of the ticket. 12 | 13 | 14 | 15 | Requirement 16 | ============= 17 | 18 | 1. webapi.vim 19 | 2. Redmine version >= 1.0.x 20 | 3. enable Redmine REST API 21 | 22 | 23 | 24 | installation 25 | ============= 26 | 27 | 1. Install webapi.vim 28 | 29 | 2. edit ~/.vimrc 30 | let g:redmine_auth_site = 'http://localhost:3000' 31 | let g:redmine_auth_key = 'secret' 32 | let g:redmine_author_id = '5' 33 | let g:redmine_project_id = '1' 34 | 35 | 36 | 37 | Command 38 | ======== 39 | 40 | * RedmineViewTicket 41 | a ticket is displayed. 42 | 43 | * RedmineViewAllTicket 44 | All tickets are displayed. 45 | 46 | * RedmineViewAssignedTicket 47 | Assigned ticket is displayed. (required g:redmine_author_id) 48 | 49 | * RedmineViewAssignedProjectTicket 50 | Assigned ticket is displayed. (required g:redmine_author_id) 51 | You can also specify Project_id. 52 | 53 | * RedmineViewMyTicket 54 | Report ticket is displayed. (required g:redmine_author_id) 55 | 56 | * RedmineViewMyProjectTicket 57 | Report ticket is displayed. (required g:redmine_author_id) 58 | You can also specify Project_id. 59 | 60 | * RedmineSearchTicket 61 | Search tickets. 62 | 63 | * RedmineSearchProject 64 | Search project. 65 | 66 | * RedmineEditTicket 67 | Edit Ticket. 68 | 69 | * RedmineAddTicket 70 | Add Ticket. 71 | 72 | * RedmineAddTicketWithDiscription 73 | Add Ticket with discription. 74 | 75 | Global Variable 76 | ======== 77 | 78 | * redmine_auth_site 79 | Redmine URL 80 | 81 | * redmine_auth_key 82 | Redmine API Key 83 | 84 | * redmine_browser 85 | Launch browser 86 | 87 | * redmine_author_id 88 | Assigned_id used in RedmineViewMyTicket/RedmineViewMyProjectTicket. 89 | 90 | * redmine_project_id 91 | used in RedmineViewMyProjectTicket. 92 | This is not required. "MyProject" will be retrieved automatically if you don't specify project_id. 93 | 94 | * redmine_project_id_remember 95 | If this variable is 1, remember projectId when add ticket. 96 | This is not required, default value is 1. 97 | 98 | * redmine_temporary_dir 99 | Set temp directory. 100 | This is not required, default value is $HOME/.redmine-vim/ 101 | 102 | ChangeLog 103 | ======== 104 | 2012-06-24 by kawaken 105 | - add RedmineViewAssignedTicket and RedmineViewAssignedProjectTicket 106 | 107 | 2012-05-06 by vsushkov 108 | - fix support new webapi 109 | 110 | 2012-02-19 by kakkyz (@kakkyz81) 111 | - fix multibyte character problem 112 | - Add RedmineAddTicket and RedmineAddTicketWithDiscription 113 | 114 | -------------------------------------------------------------------------------- /README.ja: -------------------------------------------------------------------------------- 1 | Introduction 2 | ============= 3 | 4 | vim上からRedmineのチケット情報を参照します。 5 | 6 | 7 | Function 8 | ============= 9 | 10 | 1. Redmineのチケット情報をvimで表示します 11 | 2. チケット番号を指定してURLにジャンプ出来ます. 12 | 13 | 14 | 15 | Requirement 16 | ============= 17 | 18 | 1. webapi.vim 19 | 2. Redmine version >= 1.0.x 20 | 3. Redmine REST API を有効にする 21 | 22 | 23 | 24 | installation 25 | ============= 26 | 27 | 1. webapi.vim をインストール 28 | 29 | 2. .vimrcに サーバ、 API Key を記述 30 | let g:redmine_auth_site = 'http://localhost:3000' 31 | let g:redmine_auth_key = 'secret' 32 | let g:redmine_author_id = '5' 33 | let g:redmine_project_id = '1' 34 | 35 | 36 | 37 | Command 38 | ======== 39 | 40 | * RedmineViewTicket 41 | チケットを表示します 42 | 43 | * RedmineViewAllTicket 44 | 全てのチケットを表示します 45 | 46 | * RedmineViewAssignedTicket 47 | 自分の担当チケットを表示します(要redmine_author_id) 48 | 49 | * RedmineViewAssignedProjectTicket 50 | 自分の担当チケットを表示します(要redmine_author_id) 51 | 条件にプロジェクトIDも指定できます 52 | 53 | * RedmineViewMyTicket 54 | 自分の報告チケットを表示します(要redmine_author_id) 55 | 56 | * RedmineViewMyProjectTicket 57 | 自分の報告チケットを表示します(要redmine_author_id) 58 | 条件にプロジェクトIDも指定できます 59 | 60 | * RedmineSearchTicket 61 | チケットを検索します 62 | 63 | * RedmineSearchProject 64 | プロジェクトを検索します 65 | 66 | * RedmineEditTicket 67 | チケット編集します 68 | 69 | * RedmineAddTicket 70 | チケットを追加します 71 | 72 | * RedmineAddTicketWithDiscription 73 | 本文付きでチケットを追加します 74 | 75 | Global Variable 76 | ======== 77 | 78 | * redmine_auth_site 79 | RedmineサイトURL 80 | 81 | * redmine_auth_key 82 | Redmine API Key 83 | 84 | * redmine_browser 85 | ブラウザ起動コマンド 86 | 87 | * redmine_author_id 88 | RedmineViewMyTicket/RedmineViewMyProjectTicketで利用される担当id 89 | 90 | * redmine_project_id 91 | RedmineViewMyProjectTicketで利用されるプロジェクトid 92 | 必須ではありません。指定がない場合は自動でマイプロジェクトを検索してくれます 93 | 94 | * redmine_project_id_remember 95 | 指定がある場合、チケット追加時にプロジェクトIDを一度選択するとそれを記憶します。 96 | 必須ではありません。デフォルト 1 (記憶する) 97 | 98 | * redmine_temporary_dir 99 | 必須ではありません。編集用の一時ディレクトリを指定します。 100 | デフォルト $HOME/.redmine-vim/ 101 | 102 | ChangeLog 103 | ======== 104 | 2012-06-24 by kawaken 105 | - add RedmineViewAssignedTicket and RedmineViewAssignedProjectTicket 106 | 107 | 2012-05-06 by vsushkov 108 | - fix support new webapi 109 | 110 | 2012-02-19 by kakkyz (@kakkyz81) 111 | - RedmineEditTicketでの文字化けを修正 112 | - 新規チケットを追加するRedmineAddTicket、本文を編集して追加できるRedmineAddTicketWithDiscription追加 113 | 114 | -------------------------------------------------------------------------------- /plugin/redmine.vim: -------------------------------------------------------------------------------- 1 | if exists("g:redmine_loaded") && g:redmine_loaded 2 | finish 3 | endif 4 | let g:redmine_loaded = 1 5 | if !exists('g:redmine_auth_site') 6 | let g:redmine_auth_site = 'http://localhost:3000' 7 | endif 8 | if !exists('g:redmine_auth_key') 9 | let g:redmine_auth_key = '' 10 | endif 11 | if !exists('g:redmine_browser') 12 | let g:redmine_browser = 'open -a Firefox' 13 | "let g:redmine_browser = 'C:\Program Files\Mozilla Firefox\firefox.exe' 14 | endif 15 | if !exists('g:redmine_author_id') 16 | let g:redmine_author_id = '' 17 | endif 18 | if !exists('g:redmine_project_id') 19 | let g:redmine_project_id = '' 20 | endif 21 | if !exists('g:redmine_project_id_remember') " if zero, ask project_id everytime when add a ticket. 22 | let g:redmine_project_id_remember = '1' 23 | endif 24 | if !exists('g:redmine_temporary_dir') 25 | let g:redmine_temporary_dir = $HOME . '/.redmine-vim' 26 | endif 27 | if !isdirectory(g:redmine_temporary_dir) 28 | call mkdir(g:redmine_temporary_dir, 'p') 29 | endif 30 | 31 | command RedmineViewAllTicket :call RedmineViewAllTicket() 32 | command RedmineViewMyTicket :call RedmineViewMyTicket() 33 | command RedmineViewMyProjectTicket :call RedmineViewMyProjectTicket() 34 | command RedmineViewAssignedTicket :call RedmineViewAssignedTicket() 35 | command RedmineViewAssignedProjectTicket :call RedmineViewAssignedProjectTicket() 36 | command -nargs=* RedmineSearchTicket :call RedmineSearchTicket() 37 | command -nargs=* RedmineSearchProject :call RedmineSearchProject(0) 38 | command -nargs=* RedmineEditTicket :call RedmineEditTicket() 39 | command -nargs=* RedmineViewTicket :call RedmineViewTicket() 40 | command -nargs=* RedmineAddTicket :call RedmineAddTicket() 41 | command -nargs=* RedmineAddTicketWithDiscription :call RedmineAddTicketWithDiscription() 42 | 43 | function! RedmineSearchTicket(args) 44 | let stat = RedmineAPIIssueList(a:args) 45 | if !stat 46 | echo 'issue not found' 47 | return 0 48 | endif 49 | 50 | let s:pkey = input("input issue id: ") 51 | if s:pkey != "" && s:pkey =~ '^\d*$' 52 | let s:site_path = g:redmine_auth_site .'/issues/'.s:pkey 53 | let s:ret = system(g:redmine_browser. ' '. s:site_path) 54 | else 55 | echoh None 56 | endif 57 | return 1 58 | endfunc 59 | function! RedmineAPIIssueList(args) 60 | let url = RedmineCreateCommand('issue_list', '', a:args) 61 | let ret = webapi#http#get(url) 62 | if ret.content == ' ' 63 | return 0 64 | endif 65 | 66 | let num = 0 67 | let dom = webapi#xml#parse(ret.content) 68 | for elem in dom.findAll("issue") 69 | echo "#" . elem.find("id").value() . ' ' . elem.find("subject").value() 70 | let num += 1 71 | endfor 72 | return num 73 | endfunc 74 | function! RedmineSearchProject(input_flg) 75 | let stat = RedmineAPIProjects() 76 | if !stat 77 | echo 'project not found' 78 | return 0 79 | endif 80 | 81 | if a:input_flg == 1 82 | let s:pkey = input("input project id: ") 83 | if s:pkey != "" && s:pkey =~ '^\d*$' 84 | return s:pkey 85 | endif 86 | endif 87 | return 1 88 | endfunc 89 | function! RedmineAPIProjects() 90 | let url = RedmineCreateCommand('project_list','','') 91 | let ret = webapi#http#get(url) 92 | if ret.content == ' ' 93 | return 0 94 | endif 95 | 96 | let num = 0 97 | let dom = webapi#xml#parse(ret.content) 98 | for elem in dom.findAll("project") 99 | echo "#" . elem.find("id").value() . ' ' . elem.find("name").value() 100 | let num += 1 101 | endfor 102 | return num 103 | endfunc 104 | function! RedmineViewAllTicket() 105 | call RedmineSearchTicket('') 106 | endfunc 107 | 108 | function! RedmineViewMyTicket() 109 | call RedmineSearchTicket({'author_id' : g:redmine_author_id}) 110 | endfunc 111 | 112 | function! RedmineViewAssignedTicket() 113 | call RedmineSearchTicket({'assigned_to_id' : g:redmine_author_id}) 114 | endfunc 115 | 116 | function! RedmineViewTicket(id) 117 | let url = RedmineCreateCommand('issue_list', a:id, {'include' : 'journals'}) 118 | let ret = webapi#http#get(url) 119 | if ret.content == ' ' 120 | return 0 121 | endif 122 | 123 | let num = 0 124 | let dom = webapi#xml#parse(ret.content) 125 | echo "#" . dom.find("id").value() . ' ' . dom.find("subject").value() 126 | echo "\n" 127 | echo dom.find("description").value() 128 | echo "\n" 129 | echo "--\n" 130 | for elem in dom.findAll("journal") 131 | echo elem.find("user").attr.name . ' ' . elem.find("created_on").value() 132 | echo elem.find("notes").value() 133 | echo "--\n" 134 | endfor 135 | return num 136 | endfunc 137 | 138 | function! RedmineViewMyProjectTicket() 139 | let cond = {'author_id' : g:redmine_author_id} 140 | if !empty(g:redmine_project_id) 141 | let cond['project_id'] = g:redmine_project_id 142 | call RedmineSearchTicket(cond) 143 | else 144 | let project_id = RedmineSearchProject(1) 145 | if !empty(project_id) 146 | let cond['project_id'] = project_id 147 | call RedmineSearchTicket(cond) 148 | endif 149 | endif 150 | endfunc 151 | 152 | function! RedmineViewAssignedProjectTicket() 153 | let cond = {'assigned_to_id' : g:redmine_author_id} 154 | if !empty(g:redmine_project_id) 155 | let cond['project_id'] = g:redmine_project_id 156 | call RedmineSearchTicket(cond) 157 | else 158 | let project_id = RedmineSearchProject(1) 159 | if !empty(project_id) 160 | let cond['project_id'] = project_id 161 | call RedmineSearchTicket(cond) 162 | endif 163 | endif 164 | endfunc 165 | 166 | function! RedmineCreateCommand(mode, id, args) 167 | let s:url = g:redmine_auth_site . '/' 168 | if !empty(a:mode) 169 | if a:mode == 'issue_list' 170 | if !empty(a:id) 171 | let s:url .= 'issues/' . a:id . '.xml' 172 | else 173 | let s:url .= 'issues.xml' 174 | endif 175 | elseif a:mode == 'project_list' 176 | let s:url .= 'projects.xml' 177 | elseif a:mode == 'issue_edit' 178 | let s:url .= 'issues/'. a:id .'.xml' 179 | elseif a:mode == 'issue_add' 180 | let s:url .= 'issues.xml' 181 | endif 182 | endif 183 | let s:param = [''] 184 | if !empty(g:redmine_auth_key) 185 | call add(s:param, 'key='. g:redmine_auth_key ) 186 | endif 187 | if !empty(a:args) 188 | for key in keys(a:args) 189 | call add(s:param, key . '='. a:args[key] ) 190 | endfor 191 | endif 192 | return s:url . '?' . join(s:param, '&') 193 | endfunc 194 | 195 | function! RedmineEditTicket(issue_id, text) 196 | call RedmineAPIIssueEdit(a:issue_id, a:text) 197 | return 198 | endfunc 199 | function! RedmineAPIIssueEdit(issue_id, text) 200 | let url = RedmineCreateCommand('issue_edit', a:issue_id, '') 201 | let tx = iconv(a:text, &encoding, "utf-8") 202 | let tx = substitute(tx, '&', '\&', 'g') 203 | let tx = substitute(tx, '<', '\<', 'g') 204 | let tx = substitute(tx, '>', '\>', 'g') 205 | let tx = substitute(tx, "'", '\'', 'g') 206 | let tx = substitute(tx, '"', '\"', 'g') 207 | let put_xml = ''. tx .'' 208 | echo put_xml 209 | let ret = webapi#http#post(url, put_xml, {'Content-Type' : 'text/xml'} , 'PUT') 210 | endfunc 211 | 212 | function! RedmineAddTicket(subject) 213 | " add ticket with only subject 214 | call s:setProjectId() 215 | 216 | call s:redmineAddTicketPost(a:subject, s:project_id, '') 217 | endfunc 218 | 219 | function! RedmineAddTicketWithDiscription(...) 220 | " add ticket with subject and discription(with tmp buffer) 221 | let l:subject = a:0 > 0 ? a:1 :'' 222 | call s:setProjectId() 223 | 224 | " open tempbuffer 225 | call s:setupDiscriptionBuffer() 226 | call append(0, l:subject) 227 | autocmd BufWritePost call s:redmineAddTicketWithDiscriptionWrite() | bdelete 228 | endfunc 229 | 230 | function! s:redmineAddTicketWithDiscriptionWrite() 231 | let l:subject = getline(1) 232 | let l:discription = join(getline(2, "$"),"\n") 233 | call s:redmineAddTicketPost(l:subject, s:project_id, l:discription) 234 | endfunc 235 | 236 | function! s:redmineAddTicketPost(subject, project_id, ...) 237 | let l:discription = a:0 > 0 ? a:1 : '' 238 | 239 | let url = RedmineCreateCommand('issue_add', '', '') 240 | let put_xml = '' 241 | let put_xml .= '' . a:project_id . '' 242 | let put_xml .= '' . iconv(a:subject, &encoding, "utf-8") . '' 243 | if !empty(l:discription) 244 | let put_xml .= '' . iconv(l:discription, &encoding, "utf-8") . '' 245 | endif 246 | let put_xml .= '' 247 | let ret = webapi#http#post(url, put_xml, {'Content-Type' : 'text/xml'} , 'POST') 248 | echomsg ' Add ticket "' . a:subject . '" complete.' 249 | endfunc 250 | 251 | function! s:setProjectId() 252 | if !empty(g:redmine_project_id) 253 | let s:project_id = g:redmine_project_id 254 | else 255 | if !exists('s:project_id') 256 | let s:project_id = RedmineSearchProject(1) 257 | elseif (g:redmine_project_id_remember == 0) 258 | let s:project_id = RedmineSearchProject(1) 259 | endif 260 | endif 261 | endfunc 262 | 263 | function! s:setupDiscriptionBuffer() 264 | let bufnr = bufwinnr('redmine_discription') 265 | if bufnr > 0 266 | exec bufnr.'wincmd w' 267 | else 268 | exec 'below split '.g:redmine_temporary_dir.'/redmine_discription' 269 | endif 270 | setlocal modifiable 271 | setlocal noswapfile 272 | 273 | silent %delete _ 274 | endfunc 275 | --------------------------------------------------------------------------------