├── README.md ├── autoload ├── pull_request.vim └── unite │ └── sources │ ├── pull_request.vim │ └── pull_request_file.vim └── doc └── unite-pull-request.txt /README.md: -------------------------------------------------------------------------------- 1 | # unite-pull-request 2 | 3 | unite-pull-request is a [unite.vim](https://github.com/Shougo/unite.vim "unite.vim") plugin for Viewing GitHub pull request. 4 | 5 | ## Requirement 6 | - curl 7 | - webapi-vim (https://github.com/mattn/webapi-vim) 8 | 9 | ## Install 10 | 11 | Bundle it! 12 | 13 | ```vim 14 | NeoBundle 'joker1007/unite-pull-request' 15 | ``` 16 | 17 | ## Usage 18 | 19 | Add `g:github_user` to your vimrc 20 | 21 | ```vim 22 | let g:github_user="" 23 | ``` 24 | 25 | To fetch pull request list of a repository, 26 | execute `:Unite` with `pull_request` source and argument. 27 | 28 | ```vim 29 | :Unite pull_request:owner/repository_name 30 | ``` 31 | 32 | If current directory is git repository and set github url as remote "origin", 33 | you don't need argument. 34 | 35 | ```vim 36 | :Unite pull_request 37 | ``` 38 | 39 | To fetch pull request changed file list, 40 | execute `:Unite` with `pull_request_file` source and argument. 41 | 42 | ```vim 43 | :Unite pull_request:owner/repository_name:1 44 | ``` 45 | 46 | ## ScreenShots 47 | ![スクリーンショット 2013-11-09 5.50.35.png](https://qiita-image-store.s3.amazonaws.com/0/78/a4cdd623-574f-de70-f912-de677480dd34.png) 48 | ![スクリーンショット 2013-11-09 5.51.03.png](https://qiita-image-store.s3.amazonaws.com/0/78/f65171ba-bdc8-1cda-cb80-b34a36ae8a3f.png) 49 | ![スクリーンショット 2013-11-09 5.51.22.png](https://qiita-image-store.s3.amazonaws.com/0/78/18f20f86-bb70-2fdf-673e-c809102e188e.png) 50 | -------------------------------------------------------------------------------- /autoload/pull_request.vim: -------------------------------------------------------------------------------- 1 | "============================================================================= 2 | " FILE: pull_request.vim 3 | " AUTHOR: Tomohiro Hashidate (joker1007) 4 | " License: MIT license {{{ 5 | " Permission is hereby granted, free of charge, to any person obtaining 6 | " a copy of this software and associated documentation files (the 7 | " "Software"), to deal in the Software without restriction, including 8 | " without limitation the rights to use, copy, modify, merge, publish, 9 | " distribute, sublicense, and/or sell copies of the Software, and to 10 | " permit persons to whom the Software is furnished to do so, subject to 11 | " the following conditions: 12 | " 13 | " The above copyright notice and this permission notice shall be included 14 | " in all copies or substantial portions of the Software. 15 | " 16 | " THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | " OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | " MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | " IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | " CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | " TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | " SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | " }}} 24 | "============================================================================= 25 | 26 | 27 | let s:save_cpo = &cpo 28 | set cpo&vim 29 | 30 | if !exists('g:github_token') 31 | echohl ErrorMsg | echomsg "require 'g:github_token' variables" | echohl None 32 | finish 33 | endif 34 | 35 | if !executable('curl') 36 | echohl ErrorMsg | echomsg "require 'curl' command" | echohl None 37 | finish 38 | endif 39 | 40 | if !exists('g:github_user') 41 | let g:github_user = substitute(s:system('git config --get github.user'), "\n", '', '') 42 | if strlen(g:github_user) == 0 43 | let g:github_user = $GITHUB_USER 44 | end 45 | endif 46 | 47 | if !exists('g:unite_pull_request_exclude_extensions') 48 | let g:unite_pull_request_exclude_extensions = [ 49 | \ "png", "jpg", "jpeg", "gif", "pdf", "bmp", 50 | \ "exe", "jar", "zip", "war", 51 | \ "doc", "docx", "xls", "xlsx", 52 | \] 53 | endif 54 | 55 | if !exists('g:unite_pull_request_status_mark_table') 56 | let g:unite_pull_request_status_mark_table = { 57 | \ "added" : "+", 58 | \ "modified" : "*", 59 | \ "removed" : "-", 60 | \ } 61 | endif 62 | 63 | if !exists('g:unite_pull_request_endpoint_url') 64 | let g:unite_pull_request_endpoint_url = "https://api.github.com/" 65 | endif 66 | 67 | if !exists('g:unite_pull_request_fetch_per_page_size') 68 | let g:unite_pull_request_fetch_per_page_size = 30 69 | endif 70 | 71 | function! s:github_request_header() 72 | let auth = s:GetGitHubAuthHeader() 73 | return { 74 | \ "User-Agent" : "unite-pull-request", 75 | \ "Content-type" : "application/json", 76 | \ "Authorization" : auth, 77 | \ } 78 | endfunction 79 | 80 | function! s:github_raw_access_header() 81 | let auth = s:GetGitHubAuthHeader() 82 | return { 83 | \ "User-Agent" : "unite-pull-request", 84 | \ "Content-type" : "application/vnd.github.v3.raw", 85 | \ "Accept" : "application/vnd.github.v3.raw", 86 | \ "Authorization" : auth, 87 | \ } 88 | endfunction 89 | 90 | function! s:pull_request_list_url(path, page) 91 | return g:unite_pull_request_endpoint_url . "repos/" . a:path . 92 | \ "/pulls?page=" . a:page . 93 | \ "&per_page=" . g:unite_pull_request_fetch_per_page_size 94 | endfunction 95 | 96 | function! s:pull_request_url(path, number) 97 | return g:unite_pull_request_endpoint_url . "repos/" . a:path . 98 | \ "/pulls/" . a:number 99 | endfunction 100 | 101 | function! s:pull_request_files_url(path, number, page) 102 | return s:pull_request_url(a:path, a:number) . "/files?page=" . a:page . 103 | \ "&per_page=" . g:unite_pull_request_fetch_per_page_size 104 | endfunction 105 | 106 | function! s:pull_request_comments_url(path, number) 107 | return s:pull_request_url(a:path, a:number) . "/comments" 108 | endfunction 109 | 110 | function! s:raw_file_url(repo, sha, path) 111 | return "https://raw.github.com/" . a:repo . "/" . a:sha . "/" . a:path 112 | endfunction 113 | 114 | function! s:is_exclude_file(filename) 115 | let matches = matchlist(a:filename, '\.\(\w*\)$') 116 | if len(matches) == 0 117 | return 0 118 | endif 119 | 120 | let extname = matches[1] 121 | return index(g:unite_pull_request_exclude_extensions, extname) + 1 122 | endfunction 123 | 124 | function! s:has_next_page(header) 125 | let idx = match(a:header, '^Link:') 126 | if idx == -1 127 | return 0 128 | endif 129 | 130 | let link_header = a:header[idx] 131 | if match(link_header, 'res="next"') 132 | return 1 133 | endif 134 | endfunction 135 | 136 | function! s:build_pr_info(data) 137 | return { 138 | \ "base_sha" : a:data.base.sha, 139 | \ "base_ref" : a:data.base.ref, 140 | \ "head_sha" : a:data.head.sha, 141 | \ "head_ref" : a:data.head.ref, 142 | \ "html_url" : a:data.html_url, 143 | \ "state" : a:data.state, 144 | \ "number" : a:data.number 145 | \ } 146 | endfunction 147 | 148 | function! pull_request#fetch_list(repo, page) 149 | let res = webapi#http#get(s:pull_request_list_url(a:repo, a:page), {}, s:github_request_header()) 150 | 151 | if res.status !~ "^2.*" 152 | return ['error', 'Failed to fetch pull request list'] 153 | endif 154 | 155 | let content = webapi#json#decode(res.content) 156 | let candidates = [] 157 | 158 | for pr in content 159 | let pr_info = s:build_pr_info(pr) 160 | let item = { 161 | \ "word" : "#" . pr.number . " " . pr.title, 162 | \ "source" : "pull_request", 163 | \ "action__source_name" : "pull_request_file", 164 | \ "action__source_args" : [a:repo, pr.number, 1, pr_info], 165 | \ "action__path" : "issues:repos/" . a:repo . "/issues/" . pr.number, 166 | \ "source__pull_request_info" : { 167 | \ "html_url" : pr.html_url, 168 | \ "state" : pr.state, 169 | \ "repo" : a:repo, 170 | \ "number" : pr.number, 171 | \ } 172 | \} 173 | call add(candidates, item) 174 | endfor 175 | 176 | if s:has_next_page(res.header) 177 | let next_page = { 178 | \ "word" : "=== Fetch next page ===", 179 | \ "source" : "pull_request", 180 | \ "action__source_name" : "pull_request", 181 | \ "action__source_args" : [a:repo, a:page + 1], 182 | \ } 183 | 184 | call add(candidates, next_page) 185 | endif 186 | 187 | return candidates 188 | endfunction 189 | 190 | function! pull_request#fetch_request(repo, number) 191 | let res = webapi#http#get(s:pull_request_url(a:repo, a:number), {}, s:github_request_header()) 192 | 193 | if res.status !~ "^2.*" 194 | echo 'Failed to fetch pull request' 195 | return {} 196 | endif 197 | 198 | return s:build_pr_info(webapi#json#decode(res.content)) 199 | endfunction 200 | 201 | function! pull_request#fetch_files(repo, number, page, ...) 202 | if a:0 > 0 203 | let pr_info = a:000[0] 204 | else 205 | let pr_info = pull_request#fetch_request(a:repo, a:number) 206 | endif 207 | 208 | let files_res = webapi#http#get(s:pull_request_files_url(a:repo, a:number, a:page), {}, s:github_request_header()) 209 | 210 | if files_res.status !~ "^2.*" 211 | echo 'Failed to fetch pull request files' 212 | return [] 213 | endif 214 | 215 | let files = webapi#json#decode(files_res.content) 216 | 217 | let candidates = [] 218 | 219 | for f in files 220 | if s:is_exclude_file(f.filename) 221 | continue 222 | endif 223 | 224 | let item = { 225 | \ "word" : g:unite_pull_request_status_mark_table[f.status] . " " . f.filename, 226 | \ "source" : "pull_request_file", 227 | \ "source__file_info" : { 228 | \ "filename" : f.filename, 229 | \ "status" : f.status, 230 | \ "repo" : a:repo, 231 | \ "number" : a:number, 232 | \ "base_sha" : pr_info.base_sha, 233 | \ "base_ref" : pr_info.base_ref, 234 | \ "head_sha" : pr_info.head_sha, 235 | \ "head_ref" : pr_info.head_ref, 236 | \ "sha" : f.sha, 237 | \ "blob_url" : f.blob_url, 238 | \ "raw_url" : f.raw_url, 239 | \ } 240 | \ } 241 | 242 | if has_key(f, 'patch') 243 | let item.source__file_info.patch = f.patch 244 | endif 245 | 246 | let item.source__file_info["fetch_base_file"] = function("s:fetch_base_file") 247 | let item.source__file_info["fetch_head_file"] = function("s:fetch_head_file") 248 | 249 | call add(candidates, item) 250 | endfor 251 | 252 | if s:has_next_page(files_res.header) 253 | let next_page = { 254 | \ "word" : "=== Fetch next page ===", 255 | \ "source" : "pull_request_file", 256 | \ "kind" : "source", 257 | \ "action__source_name" : "pull_request_file", 258 | \ "action__source_args" : [a:repo, a:number, a:page + 1, pr_info], 259 | \ "source__pull_request_info" : { 260 | \ "html_url" : pr_info.html_url, 261 | \ "state" : pr_info.state, 262 | \ "repo" : a:repo, 263 | \ "number" : a:number, 264 | \ } 265 | \ } 266 | 267 | call add(candidates, next_page) 268 | endif 269 | 270 | return candidates 271 | endfunction 272 | 273 | function! s:fetch_base_file() dict 274 | let raw_file_url = s:raw_file_url(self.repo, self.base_sha, self.filename) 275 | let raw_res = webapi#http#get(raw_file_url, {}, s:github_raw_access_header()) 276 | if raw_res.status !~ "^2.*" 277 | echo 'Failed to fetch pull request files' 278 | return "error" 279 | endif 280 | 281 | return raw_res.content 282 | endfunction 283 | 284 | function! s:fetch_head_file() dict 285 | let raw_file_url = s:raw_file_url(self.repo, self.head_sha, self.filename) 286 | let raw_res = webapi#http#get(raw_file_url, {}, s:github_raw_access_header()) 287 | if raw_res.status !~ "^2.*" 288 | echo 'Failed to fetch pull request files' 289 | return "error" 290 | endif 291 | 292 | return raw_res.content 293 | endfunction 294 | 295 | function! pull_request#post_review_comment(repo, number, comment_info) 296 | let json = webapi#json#encode(a:comment_info) 297 | let res = webapi#http#post(s:pull_request_comments_url(a:repo, a:number), 298 | \ json, s:github_request_header()) 299 | 300 | if res.status =~ "^2.*" 301 | echo "Commented." 302 | return 1 303 | else 304 | echo "Failed to post comment" 305 | return 0 306 | endif 307 | endfunction 308 | 309 | function! pull_request#open_comment_buffer(repo, number, comment_info) 310 | let filename = a:comment_info.path 311 | let position = a:comment_info.position 312 | let bufname = 'pull_request_comment:' . filename . '/' . position 313 | 314 | let bufnr = bufwinnr(bufname) 315 | if bufnr > 0 316 | exec bufnr.'wincmd w' 317 | else 318 | exec 'botright split ' . bufname 319 | exec '15 wincmd _' 320 | call s:init_pull_request_comment_buffer() 321 | 322 | let b:repo = a:repo 323 | let b:number = a:number 324 | let b:comment_info = a:comment_info 325 | endif 326 | endfunction 327 | 328 | function! s:init_pull_request_comment_buffer() 329 | setlocal bufhidden=wipe 330 | setlocal buftype=acwrite 331 | setlocal nobuflisted 332 | setlocal noswapfile 333 | setlocal modifiable 334 | setlocal nomodified 335 | setlocal nonumber 336 | setlocal ft=markdown 337 | 338 | if !exists('b:pull_request_comment_write_cmd') 339 | augroup PullRequestReviewComment 340 | autocmd! 341 | autocmd BufWriteCmd call s:post_comment() 342 | augroup END 343 | let b:pull_request_comment_write_cmd = 1 344 | endif 345 | 346 | :0 347 | startinsert! 348 | endfunction 349 | 350 | function! s:post_comment() 351 | let body = join(getline(1, "$"), "\n") 352 | let b:comment_info.body = body 353 | if pull_request#post_review_comment(b:repo, b:number, b:comment_info) 354 | setlocal nomodified 355 | bd! 356 | endif 357 | endfunction 358 | 359 | 360 | let s:unite_pull_request_token_file = expand(get(g:, 'unite_request_token_file', '~/.unite-pull-request')) 361 | 362 | " from gist-vim GistGetAuthHeader (https://github.com/mattn/gist-vim) {{{ 363 | " 364 | " License: BSD 365 | " Copyright 2010 by Yasuhiro Matsumoto 366 | " modification, are permitted provided that the following conditions are met: 367 | " 368 | " 1. Redistributions of source code must retain the above copyright notice, 369 | " this list of conditions and the following disclaimer. 370 | " 2. Redistributions in binary form must reproduce the above copyright notice, 371 | " this list of conditions and the following disclaimer in the documentation 372 | " and/or other materials provided with the distribution. 373 | " 374 | " THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 375 | " ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 376 | " LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 377 | " FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 378 | " REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 379 | " INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 380 | " (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 381 | " SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 382 | " HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 383 | " STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 384 | " ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 385 | " OF THE POSSIBILITY OF SUCH DAMAGE. 386 | " Original Author: Yasuhiro Matsumoto 387 | function! s:GetGitHubAuthHeader() abort 388 | let auth = '' 389 | if filereadable(s:unite_pull_request_token_file) 390 | let str = join(readfile(s:unite_pull_request_token_file), '') 391 | if type(str) == 1 392 | let auth = str 393 | endif 394 | endif 395 | if len(auth) > 0 396 | return auth 397 | endif 398 | 399 | redraw 400 | echohl WarningMsg 401 | echo 'unite-pull-request.vim requires authorization to use the GitHub API. These settings are stored in "~/.unite-pull-request". If you want to revoke, do "rm ~/.unite-pull-request".' 402 | echohl None 403 | let password = inputsecret('GitHub Password for '.g:github_user.':') 404 | if len(password) == 0 405 | let v:errmsg = 'Canceled' 406 | return '' 407 | endif 408 | let note = 'unite-pull-request.vim on '.hostname().' '.strftime('%Y/%m/%d-%H:%M:%S') 409 | let note_url = 'https://github.com/joker1007/unite-pull-request' 410 | let insecureSecret = printf('basic %s', webapi#base64#b64encode(g:github_user.':'.password)) 411 | let res = webapi#http#post(g:unite_pull_request_endpoint_url.'authorizations', webapi#json#encode({ 412 | \ "scopes" : ["repo"], 413 | \ "note" : note, 414 | \ "note_url" : note_url, 415 | \}), { 416 | \ "Content-Type" : "application/json", 417 | \ "Authorization" : insecureSecret, 418 | \}) 419 | let h = filter(res.header, 'stridx(v:val, "X-GitHub-OTP:") == 0') 420 | if len(h) 421 | let otp = inputsecret('OTP:') 422 | if len(otp) == 0 423 | let v:errmsg = 'Canceled' 424 | return '' 425 | endif 426 | let res = webapi#http#post(g:unite_pull_request_endpoint_url.'authorizations', webapi#json#encode({ 427 | \ "scopes" : ["repo"], 428 | \ "note" : note, 429 | \ "note_url" : note_url, 430 | \}), { 431 | \ "Content-Type" : "application/json", 432 | \ "Authorization" : insecureSecret, 433 | \ "X-GitHub-OTP" : otp, 434 | \}) 435 | endif 436 | let authorization = webapi#json#decode(res.content) 437 | if has_key(authorization, 'token') 438 | let secret = printf('token %s', authorization.token) 439 | call writefile([secret], s:unite_pull_request_token_file) 440 | if !(has('win32') || has('win64')) 441 | call system('chmod go= '.s:unite_pull_request_token_file) 442 | endif 443 | elseif has_key(authorization, 'message') 444 | let secret = '' 445 | let v:errmsg = authorization.message 446 | endif 447 | return secret 448 | endfunction 449 | " }}} 450 | 451 | let &cpo = s:save_cpo 452 | unlet s:save_cpo 453 | -------------------------------------------------------------------------------- /autoload/unite/sources/pull_request.vim: -------------------------------------------------------------------------------- 1 | "============================================================================= 2 | " FILE: unite/sources/pull_request.vim 3 | " AUTHOR: Tomohiro Hashidate (joker1007) 4 | " License: MIT license {{{ 5 | " Permission is hereby granted, free of charge, to any person obtaining 6 | " a copy of this software and associated documentation files (the 7 | " "Software"), to deal in the Software without restriction, including 8 | " without limitation the rights to use, copy, modify, merge, publish, 9 | " distribute, sublicense, and/or sell copies of the Software, and to 10 | " permit persons to whom the Software is furnished to do so, subject to 11 | " the following conditions: 12 | " 13 | " The above copyright notice and this permission notice shall be included 14 | " in all copies or substantial portions of the Software. 15 | " 16 | " THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | " OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | " MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | " IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | " CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | " TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | " SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | " }}} 24 | "============================================================================= 25 | 26 | let s:save_cpo = &cpo 27 | set cpo&vim 28 | 29 | let s:unite_source = {} 30 | let s:unite_source.name = 'pull_request' 31 | let s:unite_source.description = "candidates from GitHub pull requests" 32 | let s:unite_source.default_kind = 'source' 33 | let s:unite_source.default_action = 'start' 34 | let s:unite_source.action_table = {} 35 | let s:unite_source.parents = ["openable"] 36 | 37 | function! s:unite_source.gather_candidates(args, context) 38 | if len(a:args) < 1 39 | silent let remote = system("git remote -v | grep origin | cut -f 2 | tail -n 1") 40 | let path = matchlist(remote, "\\vgithub\.com[:/](.*)\\.git") 41 | if empty(path) 42 | echoerr "this source requires at least one arg (repository name)" 43 | return [] 44 | endif 45 | let repo = path[1] 46 | else 47 | let repo = a:args[0] 48 | endif 49 | 50 | let page = 1 51 | if len(a:args) == 2 52 | let page = a:args[1] 53 | endif 54 | 55 | return pull_request#fetch_list(repo, page) 56 | endfunction 57 | 58 | function! unite#sources#pull_request#define() 59 | return s:unite_source 60 | endfunction 61 | 62 | let s:action_table = {} 63 | let s:unite_source.action_table.source = s:action_table 64 | 65 | let s:action_table.browse = { 66 | \ 'description' : 'browser open pull request page', 67 | \ 'is_quit' : 0, 68 | \ } 69 | function! s:action_table.browse.func(candidate) 70 | let url = a:candidate.source__pull_request_info.html_url 71 | 72 | call openbrowser#open(url) 73 | endfunction 74 | 75 | let s:action_table.open = { 76 | \ 'description' : 'open pull request by vim-metarw-github-issue', 77 | \ 'is_selectable' : 1, 78 | \ 'is_quit' : 0, 79 | \ } 80 | function! s:action_table.open.func(candidates) 81 | if &filetype == "unite" 82 | wincmd w 83 | endif 84 | for candidate in a:candidates 85 | if buflisted(candidate.action__path) 86 | execute 'buffer' bufnr(candidate.action__path) 87 | else 88 | execute 'edit ' . candidate.action__path 89 | endif 90 | endfor 91 | endfunction 92 | 93 | let &cpo = s:save_cpo 94 | unlet s:save_cpo 95 | -------------------------------------------------------------------------------- /autoload/unite/sources/pull_request_file.vim: -------------------------------------------------------------------------------- 1 | "============================================================================= 2 | " FILE: unite/sources/pull_request_file.vim 3 | " AUTHOR: Tomohiro Hashidate (joker1007) 4 | " License: MIT license {{{ 5 | " Permission is hereby granted, free of charge, to any person obtaining 6 | " a copy of this software and associated documentation files (the 7 | " "Software"), to deal in the Software without restriction, including 8 | " without limitation the rights to use, copy, modify, merge, publish, 9 | " distribute, sublicense, and/or sell copies of the Software, and to 10 | " permit persons to whom the Software is furnished to do so, subject to 11 | " the following conditions: 12 | " 13 | " The above copyright notice and this permission notice shall be included 14 | " in all copies or substantial portions of the Software. 15 | " 16 | " THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | " OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | " MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | " IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | " CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | " TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | " SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | " }}} 24 | "============================================================================= 25 | 26 | let s:save_cpo = &cpo 27 | set cpo&vim 28 | 29 | let s:unite_source = {} 30 | let s:unite_source.name = 'pull_request_file' 31 | let s:unite_source.description = 'candidates from changed files of a pull request' 32 | let s:unite_source.default_action = { 'common' : 'diffopen' } 33 | let s:unite_source.action_table = {} 34 | let s:unite_source.syntax = 'uniteSource__PullRequest' 35 | let s:unite_source.hooks = {} 36 | 37 | function! s:init_file_setting() 38 | setlocal buftype=nofile 39 | setlocal bufhidden=wipe 40 | setlocal nobuflisted 41 | setlocal noswapfile 42 | setlocal nomodified 43 | setlocal nomodifiable 44 | setlocal readonly 45 | endfunction 46 | 47 | function! s:unite_source.gather_candidates(args, context) 48 | if len(a:args) < 2 49 | echoerr "this source requires two args (repository name, pull request number)" 50 | return [] 51 | endif 52 | 53 | let repo = a:args[0] 54 | let number = a:args[1] 55 | let page = 1 56 | 57 | if len(a:args) > 2 58 | let page = a:args[2] 59 | endif 60 | 61 | if len(a:args) > 3 62 | let pr_info = a:args[3] 63 | return pull_request#fetch_files(repo, number, page, pr_info) 64 | else 65 | return pull_request#fetch_files(repo, number, page) 66 | endif 67 | endfunction 68 | 69 | function! unite#sources#pull_request_file#define() 70 | return s:unite_source 71 | endfunction 72 | 73 | function! s:unite_source.hooks.on_syntax(args, context) 74 | execute "syntax match uniteSource_PullRequest_Added /".g:unite_pull_request_status_mark_table.added." .*/ contained containedin=uniteSource__PullRequest" 75 | execute "syntax match uniteSource_PullRequest_Removed /".g:unite_pull_request_status_mark_table.removed." .*/ contained containedin=uniteSource__PullRequest" 76 | execute "syntax match uniteSource_PullRequest_Modified /".g:unite_pull_request_status_mark_table.modified." .*/ contained containedin=uniteSource__PullRequest" 77 | 78 | highlight link uniteSource_PullRequest_Added DiffAdd 79 | highlight link uniteSource_PullRequest_Removed DiffDelete 80 | highlight link uniteSource_PullRequest_Modified DiffChange 81 | endfunction 82 | 83 | let s:action_table = {} 84 | let s:unite_source.action_table.common = s:action_table 85 | 86 | let s:action_table.diffopen = { 87 | \ 'description' : 'diff open base file and head file', 88 | \ 'is_selectable' : 1, 89 | \ 'is_quit' : 0, 90 | \ } 91 | function! s:action_table.diffopen.func(candidate) 92 | for item in a:candidate 93 | let status = item.source__file_info.status 94 | if status == "added" 95 | tabnew [nofile] 96 | if &modifiable 97 | call setline(1, "[no file]") 98 | endif 99 | call s:init_file_setting() 100 | setlocal splitright 101 | 102 | execute "vsplit " . 103 | \ item.source__file_info.head_ref . "/" . 104 | \ item.source__file_info.filename 105 | if &modifiable 106 | let head_file = item.source__file_info.fetch_head_file() 107 | silent put =head_file 108 | execute "1delete" 109 | call s:init_file_setting() 110 | call s:define_buffer_cmd(item.source__file_info) 111 | endif 112 | elseif status == "removed" 113 | silent execute "tabnew " . 114 | \ item.source__file_info.base_ref . "/" . 115 | \ item.source__file_info.filename 116 | if &modifiable 117 | let base_file = item.source__file_info.fetch_base_file() 118 | silent put =base_file 119 | execute "1delete" 120 | call s:init_file_setting() 121 | call s:define_buffer_cmd(item.source__file_info) 122 | endif 123 | setlocal splitright 124 | 125 | vsplit [deleted] 126 | if &modifiable 127 | call setline(1, "[deleted]") 128 | endif 129 | call s:init_file_setting() 130 | else 131 | silent execute "tabnew " . 132 | \ item.source__file_info.base_ref . "/" . 133 | \ item.source__file_info.filename 134 | if &modifiable 135 | let base_file = item.source__file_info.fetch_base_file() 136 | silent put =base_file 137 | execute "1delete" 138 | call s:init_file_setting() 139 | endif 140 | setlocal splitright 141 | diffthis 142 | 143 | silent execute "vsplit " . 144 | \ item.source__file_info.head_ref . "/" . 145 | \ item.source__file_info.filename 146 | if &modifiable 147 | let head_file = item.source__file_info.fetch_head_file() 148 | silent put =head_file 149 | execute "1delete" 150 | call s:init_file_setting() 151 | endif 152 | diffthis 153 | 154 | silent execute "botright split " . 155 | \ item.source__file_info.head_ref . "/" . 156 | \ item.source__file_info.filename . ".patch" 157 | if &modifiable 158 | let patch = item.source__file_info.patch 159 | silent put =patch 160 | execute "1delete" 161 | call s:init_file_setting() 162 | call s:define_buffer_cmd(item.source__file_info) 163 | endif 164 | endif 165 | endfor 166 | endfunction 167 | 168 | function! s:define_buffer_cmd(source__file_info) 169 | let b:source__file_info = a:source__file_info 170 | command! -buffer PrCommentPost 171 | \ call s:open_comment_buffer() 172 | nnoremap :PrCommentPost 173 | endfunction 174 | 175 | function! s:open_comment_buffer() 176 | let repo = b:source__file_info.repo 177 | let number = b:source__file_info.number 178 | 179 | let position = line(".") 180 | if b:source__file_info.status == "modified" 181 | let position = position - 1 182 | endif 183 | 184 | let comment_info = { 185 | \ "commit_id" : b:source__file_info.head_sha, 186 | \ "path" : b:source__file_info.filename, 187 | \ "position" : position, 188 | \ } 189 | 190 | call pull_request#open_comment_buffer(repo, number, comment_info) 191 | endfunction 192 | 193 | let &cpo = s:save_cpo 194 | unlet s:save_cpo 195 | -------------------------------------------------------------------------------- /doc/unite-pull-request.txt: -------------------------------------------------------------------------------- 1 | *unite-pull-request* Pull Request source for unite.vim 2 | 3 | Version: 0.1.0 4 | Author: Tomohiro Hashidate (joker1007) 5 | " License: MIT license {{{ 6 | " Permission is hereby granted, free of charge, to any person obtaining 7 | " a copy of this software and associated documentation files (the 8 | " "Software"), to deal in the Software without restriction, including 9 | " without limitation the rights to use, copy, modify, merge, publish, 10 | " distribute, sublicense, and/or sell copies of the Software, and to 11 | " permit persons to whom the Software is furnished to do so, subject to 12 | " the following conditions: 13 | " 14 | " The above copyright notice and this permission notice shall be included 15 | " in all copies or substantial portions of the Software. 16 | " 17 | " THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 18 | " OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | " MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | " IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 21 | " CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 22 | " TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 23 | " SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | " }}} 25 | 26 | ============================================================================== 27 | INTRODUCTION *unite-pull-request-introduction* 28 | 29 | |unite-pull-request| is a |unite.vim| plugin for Viewing GitHub pull request. 30 | 31 | Requirement: 32 | - |curl| 33 | - |webapi-vim| (https://github.com/mattn/webapi-vim) 34 | 35 | ============================================================================== 36 | USAGE *unite-pull-request-usage* 37 | 38 | Add |g:github_user| to your vimrc 39 | > 40 | let g:github_user="" 41 | < 42 | To fetch pull request list of a repository, 43 | execute |:Unite| with pull_request source and argument. 44 | > 45 | :Unite pull_request:owner/repository_name 46 | 47 | If current directory is git repository and set github url as remote "origin", 48 | you don't need argument. 49 | > 50 | :Unite pull_request 51 | 52 | To fetch pull request changed file list, 53 | execute |:Unite| with pull_request_file source and argument. 54 | > 55 | :Unite pull_request:owner/repository_name:1 56 | < 57 | ============================================================================== 58 | VARIABLES *unite-pull-request-variables* 59 | 60 | *g:unite_pull_request_endpoint_url* 61 | g:unite_pull_request_endpoint_url 62 | Specify API endpoint URL (for. Github Enterprise). 63 | Default value is following. 64 | > 65 | let g:unite_pull_request_endpoint_url = "https://api.github.com/" 66 | < 67 | *g:unite_pull_request_exclude_extensions* 68 | g:unite_pull_request_exclude_extensions 69 | Specify filtered file extension names. 70 | Default value is following. 71 | > 72 | let g:unite_pull_request_exclude_extensions = [ 73 | \ "png", "jpg", "jpeg", "gif", "pdf", "bmp", 74 | \ "exe", "jar", "zip", "war", 75 | \ "doc", "docx", "xls", "xlsx", 76 | \] 77 | < 78 | *g:unite_pull_request_status_mark_table* 79 | g:unite_pull_request_status_mark_table 80 | Specify marks that is indicates file status (added or removed 81 | or modified). 82 | Default value is following. 83 | > 84 | let g:unite_pull_request_status_mark_table = { 85 | \ "added" : "+", 86 | \ "modified" : "*", 87 | \ "removed" : "-", 88 | \ } 89 | < 90 | *g:unite_pull_request_fetch_per_page_size* 91 | g:unite_pull_request_fetch_per_page_size 92 | Specify per page count for fetch items . 93 | Default value is 30. 94 | > 95 | let g:unite_pull_request_status_mark_table = 100 96 | < 97 | ============================================================================== 98 | 99 | vim:tw=78:fo=tcq2mM:ts=8:ft=help:norl 100 | --------------------------------------------------------------------------------