├── plugin └── github_dashboard.vim ├── doc └── github_dashboard.txt └── autoload └── github_dashboard.vim /plugin/github_dashboard.vim: -------------------------------------------------------------------------------- 1 | if !has('ruby') 2 | finish 3 | endif 4 | 5 | if exists("g:loaded_github_dashboard_plugin") 6 | finish 7 | endif 8 | let g:loaded_github_dashboard_plugin = 1 9 | 10 | command! -nargs=* -complete=customlist,github_dashboard#autocomplete -bang GHDashboard call github_dashboard#open('!' != '', 'received_events', ) 11 | command! -nargs=* -complete=customlist,github_dashboard#autocomplete -bang GHActivity call github_dashboard#open('!' != '', 'events', ) 12 | -------------------------------------------------------------------------------- /doc/github_dashboard.txt: -------------------------------------------------------------------------------- 1 | github-dashboard.txt github-dashboard Last change: August 6 2015 2 | *githubdashboard* *github-dashboard* *github-dashboard-toc* 3 | GITHUB-DASHBOARD - TABLE OF CONTENTS 4 | ============================================================================== 5 | 6 | vim-github-dashboard 7 | User dashboard: :GHD! matz 8 | User activity: :GHA! matz 9 | Repository activity: :GHA! mruby/mruby 10 | Installation |github-dashboard-1| 11 | Requirements |github-dashboard-2| 12 | Mac OS X |github-dashboard-2-1| 13 | Ubuntu |github-dashboard-2-2| 14 | Windows |github-dashboard-2-3| 15 | Commands |github-dashboard-3| 16 | With authentication |github-dashboard-3-1| 17 | |github-dashboard-3-2| 18 | Without authentication (60 calls/hour limit, only public activities) 19 | Navigation |github-dashboard-4| 20 | Authentication |github-dashboard-5| 21 | Without authentication |github-dashboard-5-1| 22 | Caveat about GitHub Two-factor authentication |github-dashboard-5-2| 23 | Optional configuration |github-dashboard-6| 24 | Profiles |github-dashboard-7| 25 | Author |github-dashboard-8| 26 | License |github-dashboard-9| 27 | "Why Ruby?" |github-dashboard-10| 28 | 29 | 30 | VIM-GITHUB-DASHBOARD *vim-github-dashboard* 31 | ============================================================================== 32 | 33 | Browse GitHub events (user dashboard, user/repo activity) in Vim. 34 | 35 | 36 | < User dashboard: :GHD! matz >________________________________________________~ 37 | *github-dashboard-user-dashboard-ghd-matz* 38 | 39 | 40 | https://raw.github.com/junegunn/i/master/matz-dashboard.png 41 | 42 | 43 | < User activity: :GHA! matz >_________________________________________________~ 44 | *github-dashboard-user-activity-gha-matz* 45 | 46 | 47 | https://raw.github.com/junegunn/i/master/matz-activity.png 48 | 49 | 50 | < Repository activity: :GHA! mruby/mruby >____________________________________~ 51 | *github-dashboard-repository-activity-gha-mruby-mruby* 52 | 53 | 54 | https://raw.github.com/junegunn/i/master/mruby-activity.png 55 | 56 | (Color scheme used: {seoul256-light}{1}) 57 | 58 | {1} https://github.com/junegunn/seoul256.vim 59 | 60 | 61 | *github-dashboard-1* 62 | INSTALLATION *github-dashboard-installation* 63 | ============================================================================== 64 | 65 | Using {vim-plug}{2}: 66 | > 67 | Plug 'junegunn/vim-github-dashboard' 68 | < 69 | {2} https://github.com/junegunn/vim-plug 70 | 71 | 72 | *github-dashboard-2* 73 | REQUIREMENTS *github-dashboard-requirements* 74 | ============================================================================== 75 | 76 | 1. Your Vim must have Ruby support enabled. Check if :echo has('ruby') prints 1. 77 | 2. If you see LoadError on :ruby require 'json/pure', you need to install 78 | `json_pure` gem. 79 | 80 | 81 | < Mac OS X >__________________________________________________________________~ 82 | *github-dashboard-mac-os-x* 83 | *github-dashboard-2-1* 84 | 85 | The current version of Mac OS X is shipped with a Ruby-enabled Vim. However, 86 | by default it comes with an old Ruby (1.8.7), so you still need to install 87 | `json_pure` (or `json`) gem. 88 | > 89 | sudo gem install json_pure 90 | < 91 | If your Vim crashes, try installing a newer version of Vim with {Homebrew}{3}. 92 | 93 | {3} http://mxcl.github.io/homebrew/ 94 | 95 | 96 | < Ubuntu >____________________________________________________________________~ 97 | *github-dashboard-ubuntu* 98 | *github-dashboard-2-2* 99 | > 100 | sudo apt-get install ruby rubygems vim-nox && sudo /usr/bin/gem install json_pure 101 | < 102 | (Reference: {Installing vim with ruby support (+ruby)}{4}) 103 | 104 | {4} http://stackoverflow.com/questions/3794895/installing-vim-with-ruby-support-ruby 105 | 106 | 107 | < Windows >___________________________________________________________________~ 108 | *github-dashboard-windows* 109 | *github-dashboard-2-3* 110 | 111 | 1. Download and install the {official GVim}{5} 112 | 2. Download a newer build of GVim executable from {here}{6} and replace the 113 | existing one 114 | 3. Download and install Ruby 1.9.3 using {RubyInstaller}{7}. You must check Add 115 | Ruby executable to your PATH option. 116 | 117 | {5} http://www.vim.org/download.php#pc 118 | {6} http://wyw.dcweb.cn/#download 119 | {7} http://rubyinstaller.org/downloads/ 120 | 121 | 122 | *github-dashboard-3* 123 | COMMANDS *github-dashboard-commands* 124 | ============================================================================== 125 | 126 | 127 | < With authentication >_______________________________________________________~ 128 | *github-dashboard-with-authentication* 129 | *github-dashboard-3-1* 130 | 131 | *:GHDashboard* *:GHActivity* 132 | 133 | - `:GHDashboard` 134 | - :GHDashboard USER 135 | - `:GHActivity` 136 | - :GHActivity USER 137 | - :GHActivity USER/REPO 138 | 139 | 140 | < Without authentication (60 calls/hour limit, only public activities) >______~ 141 | *github-dashboard-without-authentication-60-calls-hour-limit-only-public-activities* 142 | 143 | *github-dashboard-3-2* 144 | 145 | - :GHDashboard! USER 146 | - :GHActivity! USER 147 | - :GHActivity! USER/REPO 148 | 149 | 150 | *github-dashboard-4* 151 | NAVIGATION *github-dashboard-navigation* 152 | ============================================================================== 153 | 154 | Use CTRL-N and CTRL-P to navigate back and forth through the links. 155 | 156 | Press `Enter` key or `double-click` on a link to open it in the browser. 157 | 158 | Press `R` to refresh the window. 159 | 160 | Press `q` to close the window. 161 | 162 | 163 | *github-dashboard-5* 164 | AUTHENTICATION *github-dashboard-authentication* 165 | ============================================================================== 166 | 167 | When you first run `:GHDashboard` or `:GHActivity` command, you will be asked 168 | to enter your GitHub username and the password. Once it is successfully 169 | authenticated, the Vim process will remember the credentials and will no more 170 | ask for them. 171 | 172 | *g:github_dashboard* 173 | 174 | If you don't want to be asked for username and password, you can set up 175 | `g:github_dashboard` variable as follows. 176 | > 177 | let g:github_dashboard = { 'username': 'you', 'password': 'secret' } 178 | < 179 | Since having plain-text password in your .vimrc is not the most secure thing 180 | you can do, it is strongly recommended that you don't put password in your Vim 181 | configuration file. 182 | 183 | As a more secure alternative, create a {Personal Access Token}{8}, export it 184 | as an environment variable and use it as a password. 185 | > 186 | # in some secure file sourced in your .bashrc, .bash_profile, .zshrc, etc. 187 | export GITHUB_TOKEN="" 188 | 189 | let g:github_dashboard = { 'username': 'you', 'password': $GITHUB_TOKEN } 190 | < 191 | {8} https://github.com/settings/tokens/new 192 | 193 | 194 | < Without authentication >____________________________________________________~ 195 | *github-dashboard-without-authentication* 196 | *github-dashboard-5-1* 197 | 198 | In fact, GitHub API allows you to browse dashboard or activity stream without 199 | authentication, though the hourly rate is limited to 60. Well, that's good 200 | enough as long as you don't check GitHub page every minute. Use bang commands 201 | then: `:GHDashboard!` and `:GHActivity!`. 202 | 203 | 204 | < Caveat about GitHub Two-factor authentication >_____________________________~ 205 | *github-dashboard-caveat-about-github-two-factor-authentication* 206 | *github-dashboard-5-2* 207 | 208 | If you have enabled {GitHub Two-factor authentication}{9}, you cannot login 209 | with your username and password. In that case, you can generate a {Personal 210 | Access Token}{10} and use it as the password. 211 | 212 | {9} https://github.com/settings/two_factor_authentication/configure 213 | {10} https://github.com/settings/applications 214 | 215 | 216 | *github-dashboard-6* 217 | OPTIONAL CONFIGURATION *github-dashboard-optional-configuration* 218 | ============================================================================== 219 | 220 | *github_dashboard#status* 221 | > 222 | let g:github_dashboard = {} 223 | 224 | " Dashboard window position 225 | " - Options: tab, top, bottom, above, below, left, right 226 | " - Default: tab 227 | let g:github_dashboard['position'] = 'top' 228 | 229 | " Disable Emoji output 230 | " - Default: only enable on terminal Vim on Mac 231 | let g:github_dashboard['emoji'] = 0 232 | 233 | " Customize emoji (see http://www.emoji-cheat-sheet.com/) 234 | let g:github_dashboard['emoji_map'] = { 235 | \ 'user_dashboard': 'blush', 236 | \ 'user_activity': 'smile', 237 | \ 'repo_activity': 'laughing', 238 | \ 'ForkEvent': 'fork_and_knife' 239 | \ } 240 | 241 | " Command to open link URLs 242 | " - Default: auto-detect 243 | let g:github_dashboard['open_command'] = 'open' 244 | 245 | " API timeout in seconds 246 | " - Default: 10, 20 247 | let g:github_dashboard['api_open_timeout'] = 10 248 | let g:github_dashboard['api_read_timeout'] = 20 249 | 250 | " Do not set statusline 251 | " - Then you can customize your own statusline with github_dashboard#status() 252 | let g:github_dashboard['statusline'] = 0 253 | 254 | " GitHub Enterprise 255 | let g:github_dashboard['api_endpoint'] = 'http://github.mycorp.com/api/v3' 256 | let g:github_dashboard['web_endpoint'] = 'http://github.mycorp.com' 257 | < 258 | 259 | *github-dashboard-7* 260 | PROFILES *github-dashboard-profiles* 261 | ============================================================================== 262 | 263 | In case you need access to GitHub Enterprise as well as the public GitHub, you 264 | might want to define multiple sets of configuration as profiles. 265 | > 266 | " Default configuration for public GitHub 267 | let g:github_dashboard = { 268 | \ 'username': 'kent' 269 | \ } 270 | 271 | " Profile named `ck` 272 | let g:github_dashboard#ck = { 273 | \ 'username': 'kent.clark', 274 | \ 'api_endpoint': 'http://github.daily-planet.com/api/v3', 275 | \ 'web_endpoint': 'http://github.daily-planet.com' 276 | \ } 277 | 278 | " Profile named `super` 279 | let g:github_dashboard#super = { 280 | \ 'username': 'superman', 281 | \ 'api_endpoint': 'http://github.justice-league.org/api/v3', 282 | \ 'web_endpoint': 'http://github.justice-league.org' 283 | \ } 284 | < 285 | Then you can access each GitHub instance like so: 286 | > 287 | GHD! 288 | 289 | " GitHub Enterprise requires authentication, so use non-bang versions 290 | GHD -ck 291 | GHA -ck lois 292 | 293 | GHD -super 294 | GHA -super batman/bmobile 295 | < 296 | 297 | *github-dashboard-8* 298 | AUTHOR *github-dashboard-author* 299 | ============================================================================== 300 | 301 | {Junegunn Choi}{11} 302 | 303 | {11} https://github.com/junegunn 304 | 305 | 306 | *github-dashboard-9* 307 | LICENSE *github-dashboard-license* 308 | ============================================================================== 309 | 310 | MIT 311 | 312 | 313 | *github-dashboard-10* 314 | "WHY RUBY?" *github-dashboard-why-ruby* 315 | ============================================================================== 316 | 317 | 1. This is a personal fun project, and I like Ruby, so why not? 318 | 2. Ruby allows me to access GitHub API without another Vim plugin or an external 319 | executable 320 | 3. Mac OS X (which I use the most) is shipped with a Ruby-enabled Vim, so it's 321 | pretty easy to set up 322 | 323 | 324 | ============================================================================== 325 | vim:tw=78:sw=2:ts=2:ft=help:norl:nowrap: 326 | -------------------------------------------------------------------------------- /autoload/github_dashboard.vim: -------------------------------------------------------------------------------- 1 | " Copyright (c) 2013 Junegunn Choi 2 | " 3 | " MIT License 4 | " 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 14 | " included in all copies or substantial portions of the Software. 15 | " 16 | " THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | " EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | " MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | " NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | " LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | " OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | " WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | if exists("g:loaded_github_dashboard") 25 | finish 26 | endif 27 | let g:loaded_github_dashboard = 1 28 | 29 | let s:passwords = {} 30 | let s:more_line = ' -- MORE --' 31 | let s:not_loaded = '' 32 | let s:history = { 'received_events': {}, 'events': {} } 33 | let s:basedir = expand(':p:h') 34 | 35 | let s:is_mac = 36 | \ has('mac') || 37 | \ has('macunix') || 38 | \ executable('uname') && 39 | \ index(['Darwin', 'Mac'], substitute(system('uname'), '\n', '', '')) != -1 40 | let s:is_win = has('win32') || has('win64') 41 | 42 | if s:is_mac 43 | let s:emoji_code = { 44 | \ "+1": [0x1f44d], 45 | \ "-1": [0x1f44e], 46 | \ "100": [0x1f4af], 47 | \ "1234": [0x1f522], 48 | \ "8ball": [0x1f3b1], 49 | \ "a": [0x1f170], 50 | \ "ab": [0x1f18e], 51 | \ "abc": [0x1f524], 52 | \ "abcd": [0x1f521], 53 | \ "accept": [0x1f251], 54 | \ "aerial_tramway": [0x1f6a1], 55 | \ "airplane": [0x2708], 56 | \ "alarm_clock": [0x23f0], 57 | \ "alien": [0x1f47d], 58 | \ "ambulance": [0x1f691], 59 | \ "anchor": [0x2693], 60 | \ "angel": [0x1f47c], 61 | \ "anger": [0x1f4a2], 62 | \ "angry": [0x1f620], 63 | \ "anguished": [0x1f627], 64 | \ "ant": [0x1f41c], 65 | \ "apple": [0x1f34e], 66 | \ "aquarius": [0x2652], 67 | \ "aries": [0x2648], 68 | \ "arrow_backward": [0x25c0], 69 | \ "arrow_double_down": [0x23ec], 70 | \ "arrow_double_up": [0x23eb], 71 | \ "arrow_down": [0x2b07], 72 | \ "arrow_down_small": [0x1f53d], 73 | \ "arrow_forward": [0x25b6], 74 | \ "arrow_heading_down": [0x2935], 75 | \ "arrow_heading_up": [0x2934], 76 | \ "arrow_left": [0x2b05], 77 | \ "arrow_lower_left": [0x2199], 78 | \ "arrow_lower_right": [0x2198], 79 | \ "arrow_right": [0x27a1], 80 | \ "arrow_right_hook": [0x21aa], 81 | \ "arrow_up": [0x2b06], 82 | \ "arrow_up_down": [0x2195], 83 | \ "arrow_up_small": [0x1f53c], 84 | \ "arrow_upper_left": [0x2196], 85 | \ "arrow_upper_right": [0x2197], 86 | \ "arrows_clockwise": [0x1f503], 87 | \ "arrows_counterclockwise": [0x1f504], 88 | \ "art": [0x1f3a8], 89 | \ "articulated_lorry": [0x1f69b], 90 | \ "astonished": [0x1f632], 91 | \ "athletic_shoe": [0x1f45f], 92 | \ "atm": [0x1f3e7], 93 | \ "b": [0x1f171], 94 | \ "baby": [0x1f476], 95 | \ "baby_bottle": [0x1f37c], 96 | \ "baby_chick": [0x1f424], 97 | \ "baby_symbol": [0x1f6bc], 98 | \ "back": [0x1f519], 99 | \ "baggage_claim": [0x1f6c4], 100 | \ "balloon": [0x1f388], 101 | \ "ballot_box_with_check": [0x2611], 102 | \ "bamboo": [0x1f38d], 103 | \ "banana": [0x1f34c], 104 | \ "bangbang": [0x203c], 105 | \ "bank": [0x1f3e6], 106 | \ "bar_chart": [0x1f4ca], 107 | \ "barber": [0x1f488], 108 | \ "baseball": [0x26be], 109 | \ "basketball": [0x1f3c0], 110 | \ "bath": [0x1f6c0], 111 | \ "bathtub": [0x1f6c1], 112 | \ "battery": [0x1f50b], 113 | \ "bear": [0x1f43b], 114 | \ "bee": [0x1f41d], 115 | \ "beer": [0x1f37a], 116 | \ "beers": [0x1f37b], 117 | \ "beetle": [0x1f41e], 118 | \ "beginner": [0x1f530], 119 | \ "bell": [0x1f514], 120 | \ "bento": [0x1f371], 121 | \ "bicyclist": [0x1f6b4], 122 | \ "bike": [0x1f6b2], 123 | \ "bikini": [0x1f459], 124 | \ "bird": [0x1f426], 125 | \ "birthday": [0x1f382], 126 | \ "black_circle": [0x26ab], 127 | \ "black_joker": [0x1f0cf], 128 | \ "black_large_square": [0x2b1b], 129 | \ "black_medium_small_square": [0x25fe], 130 | \ "black_medium_square": [0x25fc], 131 | \ "black_nib": [0x2712], 132 | \ "black_small_square": [0x25aa], 133 | \ "black_square_button": [0x1f532], 134 | \ "blossom": [0x1f33c], 135 | \ "blowfish": [0x1f421], 136 | \ "blue_book": [0x1f4d8], 137 | \ "blue_car": [0x1f699], 138 | \ "blue_heart": [0x1f499], 139 | \ "blush": [0x1f60a], 140 | \ "boar": [0x1f417], 141 | \ "boat": [0x26f5], 142 | \ "bomb": [0x1f4a3], 143 | \ "book": [0x1f4d6], 144 | \ "bookmark": [0x1f516], 145 | \ "bookmark_tabs": [0x1f4d1], 146 | \ "books": [0x1f4da], 147 | \ "boom": [0x1f4a5], 148 | \ "boot": [0x1f462], 149 | \ "bouquet": [0x1f490], 150 | \ "bow": [0x1f647], 151 | \ "bowling": [0x1f3b3], 152 | \ "boy": [0x1f466], 153 | \ "bread": [0x1f35e], 154 | \ "bride_with_veil": [0x1f470], 155 | \ "bridge_at_night": [0x1f309], 156 | \ "briefcase": [0x1f4bc], 157 | \ "broken_heart": [0x1f494], 158 | \ "bug": [0x1f41b], 159 | \ "bulb": [0x1f4a1], 160 | \ "bullettrain_front": [0x1f685], 161 | \ "bullettrain_side": [0x1f684], 162 | \ "bus": [0x1f68c], 163 | \ "busstop": [0x1f68f], 164 | \ "bust_in_silhouette": [0x1f464], 165 | \ "busts_in_silhouette": [0x1f465], 166 | \ "cactus": [0x1f335], 167 | \ "cake": [0x1f370], 168 | \ "calendar": [0x1f4c6], 169 | \ "calling": [0x1f4f2], 170 | \ "camel": [0x1f42b], 171 | \ "camera": [0x1f4f7], 172 | \ "cancer": [0x264b], 173 | \ "candy": [0x1f36c], 174 | \ "capital_abcd": [0x1f520], 175 | \ "capricorn": [0x2651], 176 | \ "car": [0x1f697], 177 | \ "card_index": [0x1f4c7], 178 | \ "carousel_horse": [0x1f3a0], 179 | \ "cat": [0x1f431], 180 | \ "cat2": [0x1f408], 181 | \ "cd": [0x1f4bf], 182 | \ "chart": [0x1f4b9], 183 | \ "chart_with_downwards_trend": [0x1f4c9], 184 | \ "chart_with_upwards_trend": [0x1f4c8], 185 | \ "checkered_flag": [0x1f3c1], 186 | \ "cherries": [0x1f352], 187 | \ "cherry_blossom": [0x1f338], 188 | \ "chestnut": [0x1f330], 189 | \ "chicken": [0x1f414], 190 | \ "children_crossing": [0x1f6b8], 191 | \ "chocolate_bar": [0x1f36b], 192 | \ "christmas_tree": [0x1f384], 193 | \ "church": [0x26ea], 194 | \ "cinema": [0x1f3a6], 195 | \ "circus_tent": [0x1f3aa], 196 | \ "city_sunrise": [0x1f307], 197 | \ "city_sunset": [0x1f306], 198 | \ "cl": [0x1f191], 199 | \ "clap": [0x1f44f], 200 | \ "clapper": [0x1f3ac], 201 | \ "clipboard": [0x1f4cb], 202 | \ "clock1": [0x1f550], 203 | \ "clock10": [0x1f559], 204 | \ "clock1030": [0x1f565], 205 | \ "clock11": [0x1f55a], 206 | \ "clock1130": [0x1f566], 207 | \ "clock12": [0x1f55b], 208 | \ "clock1230": [0x1f567], 209 | \ "clock130": [0x1f55c], 210 | \ "clock2": [0x1f551], 211 | \ "clock230": [0x1f55d], 212 | \ "clock3": [0x1f552], 213 | \ "clock330": [0x1f55e], 214 | \ "clock4": [0x1f553], 215 | \ "clock430": [0x1f55f], 216 | \ "clock5": [0x1f554], 217 | \ "clock530": [0x1f560], 218 | \ "clock6": [0x1f555], 219 | \ "clock630": [0x1f561], 220 | \ "clock7": [0x1f556], 221 | \ "clock730": [0x1f562], 222 | \ "clock8": [0x1f557], 223 | \ "clock830": [0x1f563], 224 | \ "clock9": [0x1f558], 225 | \ "clock930": [0x1f564], 226 | \ "closed_book": [0x1f4d5], 227 | \ "closed_lock_with_key": [0x1f510], 228 | \ "closed_umbrella": [0x1f302], 229 | \ "cloud": [0x2601], 230 | \ "clubs": [0x2663], 231 | \ "cn": [0x1f1e8, 0x1f1f3], 232 | \ "cocktail": [0x1f378], 233 | \ "coffee": [0x2615], 234 | \ "cold_sweat": [0x1f630], 235 | \ "collision": [0x1f4a5], 236 | \ "computer": [0x1f4bb], 237 | \ "confetti_ball": [0x1f38a], 238 | \ "confounded": [0x1f616], 239 | \ "confused": [0x1f615], 240 | \ "congratulations": [0x3297], 241 | \ "construction": [0x1f6a7], 242 | \ "construction_worker": [0x1f477], 243 | \ "convenience_store": [0x1f3ea], 244 | \ "cookie": [0x1f36a], 245 | \ "cool": [0x1f192], 246 | \ "cop": [0x1f46e], 247 | \ "copyright": [0x00a9], 248 | \ "corn": [0x1f33d], 249 | \ "couple": [0x1f46b], 250 | \ "couple_with_heart": [0x1f491], 251 | \ "couplekiss": [0x1f48f], 252 | \ "cow": [0x1f42e], 253 | \ "cow2": [0x1f404], 254 | \ "credit_card": [0x1f4b3], 255 | \ "crescent_moon": [0x1f319], 256 | \ "crocodile": [0x1f40a], 257 | \ "crossed_flags": [0x1f38c], 258 | \ "crown": [0x1f451], 259 | \ "cry": [0x1f622], 260 | \ "crying_cat_face": [0x1f63f], 261 | \ "crystal_ball": [0x1f52e], 262 | \ "cupid": [0x1f498], 263 | \ "curly_loop": [0x27b0], 264 | \ "currency_exchange": [0x1f4b1], 265 | \ "curry": [0x1f35b], 266 | \ "custard": [0x1f36e], 267 | \ "customs": [0x1f6c3], 268 | \ "cyclone": [0x1f300], 269 | \ "dancer": [0x1f483], 270 | \ "dancers": [0x1f46f], 271 | \ "dango": [0x1f361], 272 | \ "dart": [0x1f3af], 273 | \ "dash": [0x1f4a8], 274 | \ "date": [0x1f4c5], 275 | \ "de": [0x1f1e9, 0x1f1ea], 276 | \ "deciduous_tree": [0x1f333], 277 | \ "department_store": [0x1f3ec], 278 | \ "diamond_shape_with_a_dot_inside": [0x1f4a0], 279 | \ "diamonds": [0x2666], 280 | \ "disappointed": [0x1f61e], 281 | \ "disappointed_relieved": [0x1f625], 282 | \ "dizzy": [0x1f4ab], 283 | \ "dizzy_face": [0x1f635], 284 | \ "do_not_litter": [0x1f6af], 285 | \ "dog": [0x1f436], 286 | \ "dog2": [0x1f415], 287 | \ "dollar": [0x1f4b5], 288 | \ "dolls": [0x1f38e], 289 | \ "dolphin": [0x1f42c], 290 | \ "door": [0x1f6aa], 291 | \ "doughnut": [0x1f369], 292 | \ "dragon": [0x1f409], 293 | \ "dragon_face": [0x1f432], 294 | \ "dress": [0x1f457], 295 | \ "dromedary_camel": [0x1f42a], 296 | \ "droplet": [0x1f4a7], 297 | \ "dvd": [0x1f4c0], 298 | \ "e-mail": [0x1f4e7], 299 | \ "ear": [0x1f442], 300 | \ "ear_of_rice": [0x1f33e], 301 | \ "earth_africa": [0x1f30d], 302 | \ "earth_americas": [0x1f30e], 303 | \ "earth_asia": [0x1f30f], 304 | \ "egg": [0x1f373], 305 | \ "eggplant": [0x1f346], 306 | \ "eight": [0x0038], 307 | \ "eight_pointed_black_star": [0x2734], 308 | \ "eight_spoked_asterisk": [0x2733], 309 | \ "electric_plug": [0x1f50c], 310 | \ "elephant": [0x1f418], 311 | \ "email": [0x2709], 312 | \ "end": [0x1f51a], 313 | \ "envelope": [0x2709], 314 | \ "envelope_with_arrow": [0x1f4e9], 315 | \ "es": [0x1f1ea, 0x1f1f8], 316 | \ "euro": [0x1f4b6], 317 | \ "european_castle": [0x1f3f0], 318 | \ "european_post_office": [0x1f3e4], 319 | \ "evergreen_tree": [0x1f332], 320 | \ "exclamation": [0x2757], 321 | \ "expressionless": [0x1f611], 322 | \ "eyeglasses": [0x1f453], 323 | \ "eyes": [0x1f440], 324 | \ "facepunch": [0x1f44a], 325 | \ "factory": [0x1f3ed], 326 | \ "fallen_leaf": [0x1f342], 327 | \ "family": [0x1f46a], 328 | \ "fast_forward": [0x23e9], 329 | \ "fax": [0x1f4e0], 330 | \ "fearful": [0x1f628], 331 | \ "feet": [0x1f43e], 332 | \ "ferris_wheel": [0x1f3a1], 333 | \ "file_folder": [0x1f4c1], 334 | \ "fire": [0x1f525], 335 | \ "fire_engine": [0x1f692], 336 | \ "fireworks": [0x1f386], 337 | \ "first_quarter_moon": [0x1f313], 338 | \ "first_quarter_moon_with_face": [0x1f31b], 339 | \ "fish": [0x1f41f], 340 | \ "fish_cake": [0x1f365], 341 | \ "fishing_pole_and_fish": [0x1f3a3], 342 | \ "fist": [0x270a], 343 | \ "five": [0x0035], 344 | \ "flags": [0x1f38f], 345 | \ "flashlight": [0x1f526], 346 | \ "flipper": [0x1f42c], 347 | \ "floppy_disk": [0x1f4be], 348 | \ "flower_playing_cards": [0x1f3b4], 349 | \ "flushed": [0x1f633], 350 | \ "foggy": [0x1f301], 351 | \ "football": [0x1f3c8], 352 | \ "footprints": [0x1f463], 353 | \ "fork_and_knife": [0x1f374], 354 | \ "fountain": [0x26f2], 355 | \ "four": [0x0034], 356 | \ "four_leaf_clover": [0x1f340], 357 | \ "fr": [0x1f1eb, 0x1f1f7], 358 | \ "free": [0x1f193], 359 | \ "fried_shrimp": [0x1f364], 360 | \ "fries": [0x1f35f], 361 | \ "frog": [0x1f438], 362 | \ "frowning": [0x1f626], 363 | \ "fuelpump": [0x26fd], 364 | \ "full_moon": [0x1f315], 365 | \ "full_moon_with_face": [0x1f31d], 366 | \ "game_die": [0x1f3b2], 367 | \ "gb": [0x1f1ec, 0x1f1e7], 368 | \ "gem": [0x1f48e], 369 | \ "gemini": [0x264a], 370 | \ "ghost": [0x1f47b], 371 | \ "gift": [0x1f381], 372 | \ "gift_heart": [0x1f49d], 373 | \ "girl": [0x1f467], 374 | \ "globe_with_meridians": [0x1f310], 375 | \ "goat": [0x1f410], 376 | \ "golf": [0x26f3], 377 | \ "grapes": [0x1f347], 378 | \ "green_apple": [0x1f34f], 379 | \ "green_book": [0x1f4d7], 380 | \ "green_heart": [0x1f49a], 381 | \ "grey_exclamation": [0x2755], 382 | \ "grey_question": [0x2754], 383 | \ "grimacing": [0x1f62c], 384 | \ "grin": [0x1f601], 385 | \ "grinning": [0x1f600], 386 | \ "guardsman": [0x1f482], 387 | \ "guitar": [0x1f3b8], 388 | \ "gun": [0x1f52b], 389 | \ "haircut": [0x1f487], 390 | \ "hamburger": [0x1f354], 391 | \ "hammer": [0x1f528], 392 | \ "hamster": [0x1f439], 393 | \ "hand": [0x270b], 394 | \ "handbag": [0x1f45c], 395 | \ "hankey": [0x1f4a9], 396 | \ "hash": [0x0023], 397 | \ "hatched_chick": [0x1f425], 398 | \ "hatching_chick": [0x1f423], 399 | \ "headphones": [0x1f3a7], 400 | \ "hear_no_evil": [0x1f649], 401 | \ "heart": [0x2764], 402 | \ "heart_decoration": [0x1f49f], 403 | \ "heart_eyes": [0x1f60d], 404 | \ "heart_eyes_cat": [0x1f63b], 405 | \ "heartbeat": [0x1f493], 406 | \ "heartpulse": [0x1f497], 407 | \ "hearts": [0x2665], 408 | \ "heavy_check_mark": [0x2714], 409 | \ "heavy_division_sign": [0x2797], 410 | \ "heavy_dollar_sign": [0x1f4b2], 411 | \ "heavy_exclamation_mark": [0x2757], 412 | \ "heavy_minus_sign": [0x2796], 413 | \ "heavy_multiplication_x": [0x2716], 414 | \ "heavy_plus_sign": [0x2795], 415 | \ "helicopter": [0x1f681], 416 | \ "herb": [0x1f33f], 417 | \ "hibiscus": [0x1f33a], 418 | \ "high_brightness": [0x1f506], 419 | \ "high_heel": [0x1f460], 420 | \ "hocho": [0x1f52a], 421 | \ "honey_pot": [0x1f36f], 422 | \ "honeybee": [0x1f41d], 423 | \ "horse": [0x1f434], 424 | \ "horse_racing": [0x1f3c7], 425 | \ "hospital": [0x1f3e5], 426 | \ "hotel": [0x1f3e8], 427 | \ "hotsprings": [0x2668], 428 | \ "hourglass": [0x231b], 429 | \ "hourglass_flowing_sand": [0x23f3], 430 | \ "house": [0x1f3e0], 431 | \ "house_with_garden": [0x1f3e1], 432 | \ "hushed": [0x1f62f], 433 | \ "ice_cream": [0x1f368], 434 | \ "icecream": [0x1f366], 435 | \ "id": [0x1f194], 436 | \ "ideograph_advantage": [0x1f250], 437 | \ "imp": [0x1f47f], 438 | \ "inbox_tray": [0x1f4e5], 439 | \ "incoming_envelope": [0x1f4e8], 440 | \ "information_desk_person": [0x1f481], 441 | \ "information_source": [0x2139], 442 | \ "innocent": [0x1f607], 443 | \ "interrobang": [0x2049], 444 | \ "iphone": [0x1f4f1], 445 | \ "it": [0x1f1ee, 0x1f1f9], 446 | \ "izakaya_lantern": [0x1f3ee], 447 | \ "jack_o_lantern": [0x1f383], 448 | \ "japan": [0x1f5fe], 449 | \ "japanese_castle": [0x1f3ef], 450 | \ "japanese_goblin": [0x1f47a], 451 | \ "japanese_ogre": [0x1f479], 452 | \ "jeans": [0x1f456], 453 | \ "joy": [0x1f602], 454 | \ "joy_cat": [0x1f639], 455 | \ "jp": [0x1f1ef, 0x1f1f5], 456 | \ "key": [0x1f511], 457 | \ "keycap_ten": [0x1f51f], 458 | \ "kimono": [0x1f458], 459 | \ "kiss": [0x1f48b], 460 | \ "kissing": [0x1f617], 461 | \ "kissing_cat": [0x1f63d], 462 | \ "kissing_closed_eyes": [0x1f61a], 463 | \ "kissing_heart": [0x1f618], 464 | \ "kissing_smiling_eyes": [0x1f619], 465 | \ "koala": [0x1f428], 466 | \ "koko": [0x1f201], 467 | \ "kr": [0x1f1f0, 0x1f1f7], 468 | \ "lantern": [0x1f3ee], 469 | \ "large_blue_circle": [0x1f535], 470 | \ "large_blue_diamond": [0x1f537], 471 | \ "large_orange_diamond": [0x1f536], 472 | \ "last_quarter_moon": [0x1f317], 473 | \ "last_quarter_moon_with_face": [0x1f31c], 474 | \ "laughing": [0x1f606], 475 | \ "leaves": [0x1f343], 476 | \ "ledger": [0x1f4d2], 477 | \ "left_luggage": [0x1f6c5], 478 | \ "left_right_arrow": [0x2194], 479 | \ "leftwards_arrow_with_hook": [0x21a9], 480 | \ "lemon": [0x1f34b], 481 | \ "leo": [0x264c], 482 | \ "leopard": [0x1f406], 483 | \ "libra": [0x264e], 484 | \ "light_rail": [0x1f688], 485 | \ "link": [0x1f517], 486 | \ "lips": [0x1f444], 487 | \ "lipstick": [0x1f484], 488 | \ "lock": [0x1f512], 489 | \ "lock_with_ink_pen": [0x1f50f], 490 | \ "lollipop": [0x1f36d], 491 | \ "loop": [0x27bf], 492 | \ "loudspeaker": [0x1f4e2], 493 | \ "love_hotel": [0x1f3e9], 494 | \ "love_letter": [0x1f48c], 495 | \ "low_brightness": [0x1f505], 496 | \ "m": [0x24c2], 497 | \ "mag": [0x1f50d], 498 | \ "mag_right": [0x1f50e], 499 | \ "mahjong": [0x1f004], 500 | \ "mailbox": [0x1f4eb], 501 | \ "mailbox_closed": [0x1f4ea], 502 | \ "mailbox_with_mail": [0x1f4ec], 503 | \ "mailbox_with_no_mail": [0x1f4ed], 504 | \ "man": [0x1f468], 505 | \ "man_with_gua_pi_mao": [0x1f472], 506 | \ "man_with_turban": [0x1f473], 507 | \ "mans_shoe": [0x1f45e], 508 | \ "maple_leaf": [0x1f341], 509 | \ "mask": [0x1f637], 510 | \ "massage": [0x1f486], 511 | \ "meat_on_bone": [0x1f356], 512 | \ "mega": [0x1f4e3], 513 | \ "melon": [0x1f348], 514 | \ "memo": [0x1f4dd], 515 | \ "mens": [0x1f6b9], 516 | \ "metro": [0x1f687], 517 | \ "microphone": [0x1f3a4], 518 | \ "microscope": [0x1f52c], 519 | \ "milky_way": [0x1f30c], 520 | \ "minibus": [0x1f690], 521 | \ "minidisc": [0x1f4bd], 522 | \ "mobile_phone_off": [0x1f4f4], 523 | \ "money_with_wings": [0x1f4b8], 524 | \ "moneybag": [0x1f4b0], 525 | \ "monkey": [0x1f412], 526 | \ "monkey_face": [0x1f435], 527 | \ "monorail": [0x1f69d], 528 | \ "moon": [0x1f314], 529 | \ "mortar_board": [0x1f393], 530 | \ "mount_fuji": [0x1f5fb], 531 | \ "mountain_bicyclist": [0x1f6b5], 532 | \ "mountain_cableway": [0x1f6a0], 533 | \ "mountain_railway": [0x1f69e], 534 | \ "mouse": [0x1f42d], 535 | \ "mouse2": [0x1f401], 536 | \ "movie_camera": [0x1f3a5], 537 | \ "moyai": [0x1f5ff], 538 | \ "muscle": [0x1f4aa], 539 | \ "mushroom": [0x1f344], 540 | \ "musical_keyboard": [0x1f3b9], 541 | \ "musical_note": [0x1f3b5], 542 | \ "musical_score": [0x1f3bc], 543 | \ "mute": [0x1f507], 544 | \ "nail_care": [0x1f485], 545 | \ "name_badge": [0x1f4db], 546 | \ "necktie": [0x1f454], 547 | \ "negative_squared_cross_mark": [0x274e], 548 | \ "neutral_face": [0x1f610], 549 | \ "new": [0x1f195], 550 | \ "new_moon": [0x1f311], 551 | \ "new_moon_with_face": [0x1f31a], 552 | \ "newspaper": [0x1f4f0], 553 | \ "ng": [0x1f196], 554 | \ "nine": [0x0039], 555 | \ "no_bell": [0x1f515], 556 | \ "no_bicycles": [0x1f6b3], 557 | \ "no_entry": [0x26d4], 558 | \ "no_entry_sign": [0x1f6ab], 559 | \ "no_good": [0x1f645], 560 | \ "no_mobile_phones": [0x1f4f5], 561 | \ "no_mouth": [0x1f636], 562 | \ "no_pedestrians": [0x1f6b7], 563 | \ "no_smoking": [0x1f6ad], 564 | \ "non-potable_water": [0x1f6b1], 565 | \ "nose": [0x1f443], 566 | \ "notebook": [0x1f4d3], 567 | \ "notebook_with_decorative_cover": [0x1f4d4], 568 | \ "notes": [0x1f3b6], 569 | \ "nut_and_bolt": [0x1f529], 570 | \ "o": [0x2b55], 571 | \ "o2": [0x1f17e], 572 | \ "ocean": [0x1f30a], 573 | \ "octopus": [0x1f419], 574 | \ "oden": [0x1f362], 575 | \ "office": [0x1f3e2], 576 | \ "ok": [0x1f197], 577 | \ "ok_hand": [0x1f44c], 578 | \ "ok_woman": [0x1f646], 579 | \ "older_man": [0x1f474], 580 | \ "older_woman": [0x1f475], 581 | \ "on": [0x1f51b], 582 | \ "oncoming_automobile": [0x1f698], 583 | \ "oncoming_bus": [0x1f68d], 584 | \ "oncoming_police_car": [0x1f694], 585 | \ "oncoming_taxi": [0x1f696], 586 | \ "one": [0x0031], 587 | \ "open_book": [0x1f4d6], 588 | \ "open_file_folder": [0x1f4c2], 589 | \ "open_hands": [0x1f450], 590 | \ "open_mouth": [0x1f62e], 591 | \ "ophiuchus": [0x26ce], 592 | \ "orange_book": [0x1f4d9], 593 | \ "outbox_tray": [0x1f4e4], 594 | \ "ox": [0x1f402], 595 | \ "package": [0x1f4e6], 596 | \ "page_facing_up": [0x1f4c4], 597 | \ "page_with_curl": [0x1f4c3], 598 | \ "pager": [0x1f4df], 599 | \ "palm_tree": [0x1f334], 600 | \ "panda_face": [0x1f43c], 601 | \ "paperclip": [0x1f4ce], 602 | \ "parking": [0x1f17f], 603 | \ "part_alternation_mark": [0x303d], 604 | \ "partly_sunny": [0x26c5], 605 | \ "passport_control": [0x1f6c2], 606 | \ "paw_prints": [0x1f43e], 607 | \ "peach": [0x1f351], 608 | \ "pear": [0x1f350], 609 | \ "pencil": [0x1f4dd], 610 | \ "pencil2": [0x270f], 611 | \ "penguin": [0x1f427], 612 | \ "pensive": [0x1f614], 613 | \ "performing_arts": [0x1f3ad], 614 | \ "persevere": [0x1f623], 615 | \ "person_frowning": [0x1f64d], 616 | \ "person_with_blond_hair": [0x1f471], 617 | \ "person_with_pouting_face": [0x1f64e], 618 | \ "phone": [0x260e], 619 | \ "pig": [0x1f437], 620 | \ "pig2": [0x1f416], 621 | \ "pig_nose": [0x1f43d], 622 | \ "pill": [0x1f48a], 623 | \ "pineapple": [0x1f34d], 624 | \ "pisces": [0x2653], 625 | \ "pizza": [0x1f355], 626 | \ "point_down": [0x1f447], 627 | \ "point_left": [0x1f448], 628 | \ "point_right": [0x1f449], 629 | \ "point_up": [0x261d], 630 | \ "point_up_2": [0x1f446], 631 | \ "police_car": [0x1f693], 632 | \ "poodle": [0x1f429], 633 | \ "poop": [0x1f4a9], 634 | \ "post_office": [0x1f3e3], 635 | \ "postal_horn": [0x1f4ef], 636 | \ "postbox": [0x1f4ee], 637 | \ "potable_water": [0x1f6b0], 638 | \ "pouch": [0x1f45d], 639 | \ "poultry_leg": [0x1f357], 640 | \ "pound": [0x1f4b7], 641 | \ "pouting_cat": [0x1f63e], 642 | \ "pray": [0x1f64f], 643 | \ "princess": [0x1f478], 644 | \ "punch": [0x1f44a], 645 | \ "purple_heart": [0x1f49c], 646 | \ "purse": [0x1f45b], 647 | \ "pushpin": [0x1f4cc], 648 | \ "put_litter_in_its_place": [0x1f6ae], 649 | \ "question": [0x2753], 650 | \ "rabbit": [0x1f430], 651 | \ "rabbit2": [0x1f407], 652 | \ "racehorse": [0x1f40e], 653 | \ "radio": [0x1f4fb], 654 | \ "radio_button": [0x1f518], 655 | \ "rage": [0x1f621], 656 | \ "railway_car": [0x1f683], 657 | \ "rainbow": [0x1f308], 658 | \ "raised_hand": [0x270b], 659 | \ "raised_hands": [0x1f64c], 660 | \ "raising_hand": [0x1f64b], 661 | \ "ram": [0x1f40f], 662 | \ "ramen": [0x1f35c], 663 | \ "rat": [0x1f400], 664 | \ "recycle": [0x267b], 665 | \ "red_car": [0x1f697], 666 | \ "red_circle": [0x1f534], 667 | \ "registered": [0x00ae], 668 | \ "relaxed": [0x263a], 669 | \ "relieved": [0x1f60c], 670 | \ "repeat": [0x1f501], 671 | \ "repeat_one": [0x1f502], 672 | \ "restroom": [0x1f6bb], 673 | \ "revolving_hearts": [0x1f49e], 674 | \ "rewind": [0x23ea], 675 | \ "ribbon": [0x1f380], 676 | \ "rice": [0x1f35a], 677 | \ "rice_ball": [0x1f359], 678 | \ "rice_cracker": [0x1f358], 679 | \ "rice_scene": [0x1f391], 680 | \ "ring": [0x1f48d], 681 | \ "rocket": [0x1f680], 682 | \ "roller_coaster": [0x1f3a2], 683 | \ "rooster": [0x1f413], 684 | \ "rose": [0x1f339], 685 | \ "rotating_light": [0x1f6a8], 686 | \ "round_pushpin": [0x1f4cd], 687 | \ "rowboat": [0x1f6a3], 688 | \ "ru": [0x1f1f7, 0x1f1fa], 689 | \ "rugby_football": [0x1f3c9], 690 | \ "runner": [0x1f3c3], 691 | \ "running": [0x1f3c3], 692 | \ "running_shirt_with_sash": [0x1f3bd], 693 | \ "sa": [0x1f202], 694 | \ "sagittarius": [0x2650], 695 | \ "sailboat": [0x26f5], 696 | \ "sake": [0x1f376], 697 | \ "sandal": [0x1f461], 698 | \ "santa": [0x1f385], 699 | \ "satellite": [0x1f4e1], 700 | \ "satisfied": [0x1f606], 701 | \ "saxophone": [0x1f3b7], 702 | \ "school": [0x1f3eb], 703 | \ "school_satchel": [0x1f392], 704 | \ "scissors": [0x2702], 705 | \ "scorpius": [0x264f], 706 | \ "scream": [0x1f631], 707 | \ "scream_cat": [0x1f640], 708 | \ "scroll": [0x1f4dc], 709 | \ "seat": [0x1f4ba], 710 | \ "secret": [0x3299], 711 | \ "see_no_evil": [0x1f648], 712 | \ "seedling": [0x1f331], 713 | \ "seven": [0x0037], 714 | \ "shaved_ice": [0x1f367], 715 | \ "sheep": [0x1f411], 716 | \ "shell": [0x1f41a], 717 | \ "ship": [0x1f6a2], 718 | \ "shirt": [0x1f455], 719 | \ "shit": [0x1f4a9], 720 | \ "shoe": [0x1f45e], 721 | \ "shower": [0x1f6bf], 722 | \ "signal_strength": [0x1f4f6], 723 | \ "six": [0x0036], 724 | \ "six_pointed_star": [0x1f52f], 725 | \ "ski": [0x1f3bf], 726 | \ "skull": [0x1f480], 727 | \ "sleeping": [0x1f634], 728 | \ "sleepy": [0x1f62a], 729 | \ "slot_machine": [0x1f3b0], 730 | \ "small_blue_diamond": [0x1f539], 731 | \ "small_orange_diamond": [0x1f538], 732 | \ "small_red_triangle": [0x1f53a], 733 | \ "small_red_triangle_down": [0x1f53b], 734 | \ "smile": [0x1f604], 735 | \ "smile_cat": [0x1f638], 736 | \ "smiley": [0x1f603], 737 | \ "smiley_cat": [0x1f63a], 738 | \ "smiling_imp": [0x1f608], 739 | \ "smirk": [0x1f60f], 740 | \ "smirk_cat": [0x1f63c], 741 | \ "smoking": [0x1f6ac], 742 | \ "snail": [0x1f40c], 743 | \ "snake": [0x1f40d], 744 | \ "snowboarder": [0x1f3c2], 745 | \ "snowflake": [0x2744], 746 | \ "snowman": [0x26c4], 747 | \ "sob": [0x1f62d], 748 | \ "soccer": [0x26bd], 749 | \ "soon": [0x1f51c], 750 | \ "sos": [0x1f198], 751 | \ "sound": [0x1f509], 752 | \ "space_invader": [0x1f47e], 753 | \ "spades": [0x2660], 754 | \ "spaghetti": [0x1f35d], 755 | \ "sparkle": [0x2747], 756 | \ "sparkler": [0x1f387], 757 | \ "sparkles": [0x2728], 758 | \ "sparkling_heart": [0x1f496], 759 | \ "speak_no_evil": [0x1f64a], 760 | \ "speaker": [0x1f50a], 761 | \ "speech_balloon": [0x1f4ac], 762 | \ "speedboat": [0x1f6a4], 763 | \ "star": [0x2b50], 764 | \ "star2": [0x1f31f], 765 | \ "stars": [0x1f303], 766 | \ "station": [0x1f689], 767 | \ "statue_of_liberty": [0x1f5fd], 768 | \ "steam_locomotive": [0x1f682], 769 | \ "stew": [0x1f372], 770 | \ "straight_ruler": [0x1f4cf], 771 | \ "strawberry": [0x1f353], 772 | \ "stuck_out_tongue": [0x1f61b], 773 | \ "stuck_out_tongue_closed_eyes": [0x1f61d], 774 | \ "stuck_out_tongue_winking_eye": [0x1f61c], 775 | \ "sun_with_face": [0x1f31e], 776 | \ "sunflower": [0x1f33b], 777 | \ "sunglasses": [0x1f60e], 778 | \ "sunny": [0x2600], 779 | \ "sunrise": [0x1f305], 780 | \ "sunrise_over_mountains": [0x1f304], 781 | \ "surfer": [0x1f3c4], 782 | \ "sushi": [0x1f363], 783 | \ "suspension_railway": [0x1f69f], 784 | \ "sweat": [0x1f613], 785 | \ "sweat_drops": [0x1f4a6], 786 | \ "sweat_smile": [0x1f605], 787 | \ "sweet_potato": [0x1f360], 788 | \ "swimmer": [0x1f3ca], 789 | \ "symbols": [0x1f523], 790 | \ "syringe": [0x1f489], 791 | \ "tada": [0x1f389], 792 | \ "tanabata_tree": [0x1f38b], 793 | \ "tangerine": [0x1f34a], 794 | \ "taurus": [0x2649], 795 | \ "taxi": [0x1f695], 796 | \ "tea": [0x1f375], 797 | \ "telephone": [0x260e], 798 | \ "telephone_receiver": [0x1f4de], 799 | \ "telescope": [0x1f52d], 800 | \ "tennis": [0x1f3be], 801 | \ "tent": [0x26fa], 802 | \ "thought_balloon": [0x1f4ad], 803 | \ "three": [0x0033], 804 | \ "thumbsdown": [0x1f44e], 805 | \ "thumbsup": [0x1f44d], 806 | \ "ticket": [0x1f3ab], 807 | \ "tiger": [0x1f42f], 808 | \ "tiger2": [0x1f405], 809 | \ "tired_face": [0x1f62b], 810 | \ "tm": [0x2122], 811 | \ "toilet": [0x1f6bd], 812 | \ "tokyo_tower": [0x1f5fc], 813 | \ "tomato": [0x1f345], 814 | \ "tongue": [0x1f445], 815 | \ "top": [0x1f51d], 816 | \ "tophat": [0x1f3a9], 817 | \ "tractor": [0x1f69c], 818 | \ "traffic_light": [0x1f6a5], 819 | \ "train": [0x1f683], 820 | \ "train2": [0x1f686], 821 | \ "tram": [0x1f68a], 822 | \ "triangular_flag_on_post": [0x1f6a9], 823 | \ "triangular_ruler": [0x1f4d0], 824 | \ "trident": [0x1f531], 825 | \ "triumph": [0x1f624], 826 | \ "trolleybus": [0x1f68e], 827 | \ "trophy": [0x1f3c6], 828 | \ "tropical_drink": [0x1f379], 829 | \ "tropical_fish": [0x1f420], 830 | \ "truck": [0x1f69a], 831 | \ "trumpet": [0x1f3ba], 832 | \ "tshirt": [0x1f455], 833 | \ "tulip": [0x1f337], 834 | \ "turtle": [0x1f422], 835 | \ "tv": [0x1f4fa], 836 | \ "twisted_rightwards_arrows": [0x1f500], 837 | \ "two": [0x0032], 838 | \ "two_hearts": [0x1f495], 839 | \ "two_men_holding_hands": [0x1f46c], 840 | \ "two_women_holding_hands": [0x1f46d], 841 | \ "u5272": [0x1f239], 842 | \ "u5408": [0x1f234], 843 | \ "u55b6": [0x1f23a], 844 | \ "u6307": [0x1f22f], 845 | \ "u6708": [0x1f237], 846 | \ "u6709": [0x1f236], 847 | \ "u6e80": [0x1f235], 848 | \ "u7121": [0x1f21a], 849 | \ "u7533": [0x1f238], 850 | \ "u7981": [0x1f232], 851 | \ "u7a7a": [0x1f233], 852 | \ "uk": [0x1f1ec, 0x1f1e7], 853 | \ "umbrella": [0x2614], 854 | \ "unamused": [0x1f612], 855 | \ "underage": [0x1f51e], 856 | \ "unlock": [0x1f513], 857 | \ "up": [0x1f199], 858 | \ "us": [0x1f1fa, 0x1f1f8], 859 | \ "v": [0x270c], 860 | \ "vertical_traffic_light": [0x1f6a6], 861 | \ "vhs": [0x1f4fc], 862 | \ "vibration_mode": [0x1f4f3], 863 | \ "video_camera": [0x1f4f9], 864 | \ "video_game": [0x1f3ae], 865 | \ "violin": [0x1f3bb], 866 | \ "virgo": [0x264d], 867 | \ "volcano": [0x1f30b], 868 | \ "vs": [0x1f19a], 869 | \ "walking": [0x1f6b6], 870 | \ "waning_crescent_moon": [0x1f318], 871 | \ "waning_gibbous_moon": [0x1f316], 872 | \ "warning": [0x26a0], 873 | \ "watch": [0x231a], 874 | \ "water_buffalo": [0x1f403], 875 | \ "watermelon": [0x1f349], 876 | \ "wave": [0x1f44b], 877 | \ "wavy_dash": [0x3030], 878 | \ "waxing_crescent_moon": [0x1f312], 879 | \ "waxing_gibbous_moon": [0x1f314], 880 | \ "wc": [0x1f6be], 881 | \ "weary": [0x1f629], 882 | \ "wedding": [0x1f492], 883 | \ "whale": [0x1f433], 884 | \ "whale2": [0x1f40b], 885 | \ "wheelchair": [0x267f], 886 | \ "white_check_mark": [0x2705], 887 | \ "white_circle": [0x26aa], 888 | \ "white_flower": [0x1f4ae], 889 | \ "white_large_square": [0x2b1c], 890 | \ "white_medium_small_square": [0x25fd], 891 | \ "white_medium_square": [0x25fb], 892 | \ "white_small_square": [0x25ab], 893 | \ "white_square_button": [0x1f533], 894 | \ "wind_chime": [0x1f390], 895 | \ "wine_glass": [0x1f377], 896 | \ "wink": [0x1f609], 897 | \ "wolf": [0x1f43a], 898 | \ "woman": [0x1f469], 899 | \ "womans_clothes": [0x1f45a], 900 | \ "womans_hat": [0x1f452], 901 | \ "womens": [0x1f6ba], 902 | \ "worried": [0x1f61f], 903 | \ "wrench": [0x1f527], 904 | \ "x": [0x274c], 905 | \ "yellow_heart": [0x1f49b], 906 | \ "yen": [0x1f4b4], 907 | \ "yum": [0x1f60b], 908 | \ "zap": [0x26a1], 909 | \ "zero": [0x0030], 910 | \ "zzz": [0x1f4a4] 911 | \ } 912 | else 913 | let s:emoji_code = {} 914 | endif 915 | 916 | let s:emoji_map = { 917 | \ 'CommitCommentEvent': 'speech_balloon', 918 | \ 'CreateEvent': 'sparkles', 919 | \ 'DeleteEvent': 'x', 920 | \ 'DownloadEvent': 'paperclip', 921 | \ 'FollowEvent': 'green_heart', 922 | \ 'ForkEvent': 'fork_and_knife', 923 | \ 'ForkApplyEvent': 'fork_and_knife', 924 | \ 'GistEvent': 'pencil', 925 | \ 'GollumEvent': 'pencil', 926 | \ 'IssueCommentEvent': 'speech_balloon', 927 | \ 'IssuesEvent': 'exclamation', 928 | \ 'MemberEvent': 'busts_in_silhouette', 929 | \ 'PublicEvent': 'tada', 930 | \ 'PullRequestEvent': 'angel', 931 | \ 'PullRequestReviewCommentEvent': 'speech_balloon', 932 | \ 'PullRequestReviewEvent': 'hand', 933 | \ 'PushEvent': 'dango', 934 | \ 'ReleaseEvent': 'bookmark', 935 | \ 'TeamAddEvent': 'busts_in_silhouette', 936 | \ 'WatchEvent': 'star', 937 | \ 'user_dashboard': 'herb', 938 | \ 'user_activity': 'cherry_blossom', 939 | \ 'repo_activity': 'hibiscus' 940 | \} 941 | 942 | function! s:emoji(name, ...) 943 | if b:github_emoji && has_key(s:emoji_code, tolower(a:name)) 944 | let e = s:emoji_code[tolower(a:name)] 945 | return join(map(copy(e), 'nr2char(v:val)'), '') . repeat(' ', 1 + (a:0 > 0 ? a:1 : 0)) 946 | else 947 | return '' 948 | endif 949 | endfunction 950 | 951 | function! s:emoji_for(type, pad) 952 | let custom_map = s:option('emoji_map', {}) 953 | if !empty(custom_map) 954 | " TODO inefficient 955 | let emoji_map = extend(copy(s:emoji_map), custom_map) 956 | return s:emoji(get(emoji_map, a:type, ''), a:pad) 957 | else 958 | return s:emoji(get(s:emoji_map, a:type, ''), a:pad) 959 | endif 960 | endfunction 961 | 962 | let s:original_statusline = &statusline 963 | 964 | function! s:password(profile, username) 965 | let fromopt = s:option(a:profile, 'password', '') 966 | return empty(fromopt) ? get(s:passwords, a:profile.'/'.a:username, '') : fromopt 967 | endfunction 968 | 969 | function! s:remember_password(profile, username, password) 970 | let s:passwords[a:profile.'/'.a:username] = a:password 971 | endfunction 972 | 973 | function! s:forget_password(profile, username) 974 | silent! call remove(s:passwords, a:profile.'/'.a:username) 975 | endfunction 976 | 977 | function! s:option(...) 978 | if a:0 == 2 979 | let profile = get(b:, 'github_profile', '') 980 | let [key, default] = a:000 981 | elseif a:0 == 3 982 | let [profile, key, default] = a:000 983 | endif 984 | 985 | let options = get(g:, 'github_dashboard' . (empty(profile) ? '' : ('#' . profile)), {}) 986 | return get(options, key, default) 987 | endfunction 988 | 989 | function! s:init_tab(...) 990 | let b:github_index = 0 991 | let b:github_error = 0 992 | let b:github_links = {} 993 | let b:github_emoji = s:is_mac && ((!has('gui_running') && s:option('emoji', 2) != 0) || s:option('emoji', 2) == 1) 994 | let b:github_indent = repeat(' ', b:github_emoji ? 11 : 8) 995 | let b:github_api_endpoint = s:option('api_endpoint', 'https://api.github.com') 996 | let b:github_web_endpoint = s:option('web_endpoint', 'https://github.com') 997 | 998 | if a:0 == 2 999 | setlocal buftype=nofile noswapfile nowrap nonu cursorline foldmethod=syntax 1000 | call s:define_maps() 1001 | setf github-dashboard 1002 | 1003 | let [what, type] = a:000 1004 | let elems = len(filter(split(what, '/', 1), '!empty(v:val)')) 1005 | if elems == 0 || elems > 2 | echoerr "Invalid username or repository" | return 0 | endif 1006 | let path = elems == 1 ? '/users/' : '/repos/' 1007 | let b:github_init_url = b:github_api_endpoint .path.what. "/" .type 1008 | if type == 'received_events' 1009 | if elems > 1 | echoerr "Use :GHActivity command instead" | return 0 | endif 1010 | let b:github_statusline = ['Dashboard', what] 1011 | let prefix = s:emoji_for('user_dashboard', 1) 1012 | elseif type == 'events' 1013 | let b:github_statusline = ['Activity', what] 1014 | let prefix = s:emoji_for(elems == 1 ? 'user_activity' : 'repo_activity', 1) 1015 | else 1016 | echoerr "Invalid type" 1017 | return 0 1018 | endif 1019 | 1020 | " Assign buffer name 1021 | let bufname_prefix = '['.prefix.split(what, '/')[-1].']' 1022 | let bufname = bufname_prefix 1023 | let bufidx = 2 1024 | while buflisted(bufname) 1025 | let bufname = bufname_prefix . '('. bufidx .')' 1026 | let bufidx = bufidx + 1 1027 | endwhile 1028 | silent! execute "f ".fnameescape(bufname) 1029 | endif 1030 | let b:github_more_url = b:github_init_url 1031 | 1032 | if s:option('statusline', 1) 1033 | setlocal statusline=%!github_dashboard#statusline() 1034 | endif 1035 | 1036 | syntax clear 1037 | syntax region githubTitle start=/^ \{0,2}[0-9]/ end="\n" oneline contains=githubNumber,Keyword,githubRepo,githubUser,githubTime,githubRef,githubCommit,githubTag,githubBranch,githubGist,githubRelease 1038 | syntax match githubNumber /^ \{0,2}[0-9]\{-1,})/ contained 1039 | syntax match githubTime /(.\{-1,})$/ contained 1040 | syntax match githubSHA /^\s\+\[[0-9a-fA-F]\{4,}\]/ 1041 | syntax match githubEdit /\(^\s\+Edited \)\@<=\[.\{-}\]/ 1042 | syntax match githubUser /\[[^/\]]\{-1,}\]/ contained 1043 | syntax match githubRepo /\[[^/\]]\{-1,}\/[^/\]@]\{-1,}\]/ contained 1044 | syntax match githubCommit /\[[^/\]]\{-1,}\/[^/\]@]\{-1,}@[0-9a-fA-Z]\{-1,}\]/ contained 1045 | syntax match githubTag /\(tag \)\@<=\[.\{-1,}\]/ contained 1046 | syntax match githubBranch /\(branch \)\@<=\[.\{-1,}\]/ contained 1047 | syntax match githubBranch /\(pushed to \)\@<=\[.\{-1,}\]/ contained 1048 | syntax match githubGist /\(a gist \)\@<=\[.\{-1,}\]/ contained 1049 | syntax match githubRelease /\(released \)\@<=\[.\{-1,}\]/ contained 1050 | 1051 | syntax region githubFoldBlock start=/\%(\_^ \{4,}.*\n\)\{5}/ms=s+1 end=/\%(^ \{,4}\S\)\@=/ contains=githubFoldBlockLine2 1052 | syntax region githubFoldBlockLine2 start=/^ \{4,}/ms=e+1 end=/\%(^ \{,4}\S\)\@=/ contained contains=githubFoldBlockLine3 keepend 1053 | syntax region githubFoldBlockLine3 start=/^ \{4,}/ms=e+1 end=/\%(^ \{,4}\S\)\@=/ contained contains=githubFoldBlockLine4 keepend 1054 | syntax region githubFoldBlockLine4 start=/^ \{4,}/ms=e+1 end=/\%(^ \{,4}\S\)\@=/ contained contains=githubFoldBlockLine5 keepend 1055 | syntax region githubFoldBlockLine5 start=/^ \{4,}/ms=e+1 end=/\%(^ \{,4}\S\)\@=/ contained keepend fold 1056 | 1057 | hi def link githubNumber Number 1058 | hi def link githubUser String 1059 | hi def link githubRepo Identifier 1060 | hi def link githubRef Special 1061 | hi def link githubRelease Label 1062 | hi def link githubTag Label 1063 | hi def link githubBranch Label 1064 | hi def link githubEdit Constant 1065 | hi def link githubTime Comment 1066 | hi def link githubSHA Float 1067 | hi def link githubCommit Special 1068 | hi def link githubGist Identifier 1069 | execute 'syntax match githubKeyword /'.s:more_line.'/' 1070 | syntax match githubKeyword /^Loading.*/ 1071 | syntax match githubKeyword /^Reloading.*/ 1072 | syntax match githubFailure /^Failed.*/ 1073 | hi def link githubKeyword Conditional 1074 | hi def link githubFailure Exception 1075 | 1076 | return 1 1077 | endfunction 1078 | 1079 | function! s:refresh() 1080 | call s:init_tab() 1081 | setlocal modifiable 1082 | normal! gg"_dG 1083 | setlocal nomodifiable 1084 | 1085 | try 1086 | call s:call_ruby('Reloading GitHub event stream ...') 1087 | catch 1088 | let b:github_error = 1 1089 | endtry 1090 | if b:github_error 1091 | call setline(line('$'), 'Failed to load events. Press R to reload.') 1092 | setlocal nomodifiable 1093 | return 1094 | endif 1095 | endfunction 1096 | 1097 | function! s:open(profile, what, type) 1098 | let pos = s:option('position', 'tab') 1099 | if pos ==? 'tab' 1100 | tabnew 1101 | elseif pos ==? 'top' 1102 | topleft new 1103 | elseif pos ==? 'bottom' 1104 | botright new 1105 | elseif pos ==? 'above' 1106 | aboveleft new 1107 | elseif pos ==? 'below' 1108 | belowright new 1109 | elseif pos ==? 'left' 1110 | vertical new 1111 | elseif pos ==? 'right' 1112 | vertical rightbelow new 1113 | else 1114 | echoerr "Invalid position: ". pos 1115 | tabnew 1116 | return 0 1117 | endif 1118 | 1119 | let b:github_profile = a:profile 1120 | return s:init_tab(a:what, a:type) 1121 | endfunction 1122 | 1123 | function! s:call_ruby(msg) 1124 | if !empty(s:not_loaded) 1125 | echoerr s:not_loaded 1126 | return 1127 | endif 1128 | 1129 | setlocal modifiable 1130 | call setline(line('$'), a:msg) 1131 | redraw! 1132 | ruby GitHubDashboard.more 1133 | if !b:github_error 1134 | setlocal nomodifiable 1135 | end 1136 | syntax sync minlines=0 1137 | endfunction 1138 | 1139 | function! github_dashboard#open(auth, type, ...) 1140 | if !empty(s:not_loaded) 1141 | echoerr s:not_loaded 1142 | return 1143 | endif 1144 | 1145 | let profile = substitute(get(filter(copy(a:000), 'stridx(v:val, "-") == 0'), -1, ''), '^-*', '', '') 1146 | if !empty(profile) && !exists('g:github_dashboard#'.profile) 1147 | echoerr 'Profile not defined: '. profile 1148 | return 1149 | endif 1150 | 1151 | let args = filter(copy(a:000), 'stridx(v:val, "-") != 0') 1152 | let username = s:option(profile, 'username', '') 1153 | if a:auth 1154 | if empty(username) 1155 | call inputsave() 1156 | let username = input('Enter GitHub username: ') 1157 | call inputrestore() 1158 | if empty(username) | echo "Empty username" | return | endif 1159 | endif 1160 | 1161 | let password = s:password(profile, username) 1162 | if empty(password) 1163 | call inputsave() 1164 | let password = inputsecret('Enter GitHub password: ') 1165 | call inputrestore() 1166 | if empty(password) | echo "Empty password" | return | endif 1167 | call s:remember_password(profile, username, password) 1168 | endif 1169 | else 1170 | let password = '' 1171 | endif 1172 | 1173 | let who = get(args, 0, username) 1174 | if empty(who) | echo "Username not given" | return | endif 1175 | 1176 | if !s:open(profile, who, a:type) 1177 | bd 1178 | return 1179 | endif 1180 | 1181 | let b:github_username = username 1182 | let b:github_password = password 1183 | 1184 | try 1185 | call s:call_ruby('Loading GitHub event stream ...') 1186 | catch /^Vim:Interrupt$/ 1187 | bd 1188 | return 1189 | catch 1190 | bd 1191 | throw 'Error: '.v:exception 1192 | endtry 1193 | 1194 | let s:history[a:type][who] = 1 1195 | endfunction 1196 | 1197 | function! s:define_maps() 1198 | nnoremap (ghd-quit) :bd 1199 | nnoremap (ghd-refresh) :call refresh() 1200 | nnoremap (ghd-action) :call action() 1201 | nnoremap (ghd-action) :call action() 1202 | nnoremap (ghd-action) :call action() 1203 | nnoremap (ghd-next) :silent! call next_item('') 1204 | nnoremap (ghd-prev) :silent! call next_item('b') 1205 | nmap q (ghd-quit) 1206 | nmap R (ghd-refresh) 1207 | nmap (ghd-action) 1208 | nmap o (ghd-action) 1209 | nmap <2-LeftMouse> (ghd-action) 1210 | nmap (ghd-next) 1211 | nmap (ghd-prev) 1212 | endfunction 1213 | 1214 | function! s:find_url() 1215 | let line = getline(line('.')) 1216 | let nth = 0 1217 | let start = 0 1218 | let col = col('.') - 1 1219 | while 1 1220 | let idx = match(line, '\[.\{-}\]', start) 1221 | if idx == -1 || idx > col | return '' | endif 1222 | 1223 | let eidx = match(line, '\[.\{-}\zs\]', start) 1224 | if col >= idx && col <= eidx && has_key(b:github_links, line('.')) 1225 | return get(b:github_links[line('.')], nth, '') 1226 | endif 1227 | 1228 | let start = eidx + 1 1229 | let nth = nth + 1 1230 | endwhile 1231 | return '' 1232 | endfunction 1233 | 1234 | function! s:open_url(url) 1235 | let cmd = s:option('open_command', '') 1236 | if empty(cmd) 1237 | if s:is_mac 1238 | let cmd = 'open' 1239 | elseif s:is_win 1240 | execute ':silent !start rundll32 url.dll,FileProtocolHandler' 1241 | \ shellescape(fnameescape(a:url)) 1242 | return 1243 | elseif executable('xdg-open') 1244 | let cmd = 'xdg-open' 1245 | else 1246 | echo "Cannot determine command to open: ". a:url 1247 | return 1248 | endif 1249 | silent! call system(cmd . ' ' . shellescape(a:url)) 1250 | return 1251 | endif 1252 | execute ':silent !' . cmd . ' ' . shellescape(fnameescape(a:url)) 1253 | redraw! 1254 | endfunction 1255 | 1256 | function! github_dashboard#status() 1257 | if exists('b:github_statusline') 1258 | let [type, what] = b:github_statusline 1259 | return { 'type': type, 'what': what, 'url': s:find_url() } 1260 | else 1261 | return {} 1262 | end 1263 | endfunction 1264 | 1265 | function! github_dashboard#statusline() 1266 | if exists('b:github_statusline') 1267 | let prefix = '[GitHub '.join(b:github_statusline, ': ').']' 1268 | let url = s:find_url() 1269 | if empty(url) 1270 | return prefix 1271 | else 1272 | return prefix .' '. url 1273 | endif 1274 | endif 1275 | return s:original_statusline 1276 | endfunction 1277 | 1278 | function! github_dashboard#autocomplete(arg, cmd, cur) 1279 | let type = (a:cmd =~ '^GHA') ? 'events' : 'received_events' 1280 | return filter(keys(s:history[type]), 'v:val =~ "^'. escape(a:arg, '"') .'"') 1281 | endfunction 1282 | 1283 | function! s:action() 1284 | let line = getline(line('.')) 1285 | if line == s:more_line 1286 | try 1287 | call s:call_ruby('Loading ...') 1288 | catch /^Vim:Interrupt$/ 1289 | let b:github_error = 1 1290 | endtry 1291 | 1292 | if b:github_error 1293 | call setline(line('$'), s:more_line) 1294 | setlocal nomodifiable 1295 | endif 1296 | return 1297 | endif 1298 | 1299 | let url = s:find_url() 1300 | if !empty(url) 1301 | call s:open_url(url) 1302 | endif 1303 | endfunction 1304 | 1305 | function! s:next_item(flags) 1306 | call search( 1307 | \ '\(^ *-- \zsMORE\)\|' . 1308 | \ '\(^ *\[\zs[0-9a-fA-F]\{4,}\]\)\|' . 1309 | \ '\(^ *Edited \[\zs\)\|' . 1310 | \ '\(\(^ \{0,2}[0-9].\{-}\)\@<=\[\zs\)', a:flags) 1311 | endfunction 1312 | 1313 | " {{{ 1314 | ruby << EOF 1315 | require 'rubygems' rescue nil # 1.9.1 1316 | begin 1317 | require 'json/pure' 1318 | rescue LoadError 1319 | begin 1320 | require 'json' 1321 | rescue LoadError 1322 | VIM::command("let s:not_loaded = 'JSON gem is not installed. try: sudo gem install json_pure'") 1323 | end 1324 | end 1325 | 1326 | require 'net/https' 1327 | require 'time' 1328 | 1329 | module GitHubDashboard 1330 | class << self 1331 | def fetch uri, username, password 1332 | tried = false 1333 | begin 1334 | req = Net::HTTP::Get.new(uri.request_uri, 'User-Agent' => 'vim') 1335 | req.basic_auth username, password unless password.empty? 1336 | 1337 | api_endpoint = URI(VIM::evaluate('b:github_api_endpoint')) 1338 | http = Net::HTTP.new(api_endpoint.host, uri.port) 1339 | http.use_ssl = api_endpoint.scheme == 'https' 1340 | http.ca_file = ENV['SSL_CERT_FILE'] if ENV['SSL_CERT_FILE'] 1341 | ot = VIM::evaluate("s:option('api_open_timeout', 10)").to_i 1342 | rt = VIM::evaluate("s:option('api_read_timeout', 20)").to_i 1343 | http.open_timeout = ot 1344 | http.read_timeout = rt 1345 | 1346 | http.request req 1347 | rescue OpenSSL::SSL::SSLError 1348 | unless tried 1349 | # https://gist.github.com/pweldon/767249 1350 | tried = true 1351 | certpath = File.join(VIM::evaluate("s:basedir"), 'cacert.pem') 1352 | unless File.exists?(certpath) 1353 | File.open(certpath, 'w') { |f| 1354 | Net::HTTP.start('curl.haxx.se', 80) do |http| 1355 | http.open_timeout = ot 1356 | http.read_timeout = rt 1357 | res = http.get '/ca/cacert.pem' 1358 | f << res.body 1359 | end 1360 | } 1361 | end 1362 | ENV['SSL_CERT_FILE'] = certpath 1363 | retry 1364 | end 1365 | raise 1366 | end 1367 | end 1368 | 1369 | def more 1370 | if 0 == VIM::evaluate('has("nvim")') 1371 | main = Thread.current 1372 | watcher = Thread.new { 1373 | while VIM::evaluate('getchar(1)') 1374 | sleep 0.1 1375 | end 1376 | main.kill 1377 | } 1378 | end 1379 | overbose = $VERBOSE 1380 | $VERBOSE = nil 1381 | username = VIM::evaluate('b:github_username') 1382 | password = VIM::evaluate('b:github_password') 1383 | uri = URI(VIM::evaluate('b:github_more_url')) 1384 | prefix = VIM::evaluate('b:github_web_endpoint') 1385 | 1386 | res = fetch uri, username, password 1387 | if res.code !~ /^2/ 1388 | if %w[401 403].include? res.code 1389 | # Invalidate credentials 1390 | VIM::command(%[call s:forget_password(b:github_profile, b:github_username)]) 1391 | VIM::command(%[let b:github_username = '']) 1392 | VIM::command(%[let b:github_password = '']) 1393 | end 1394 | error "#{JSON.parse(res.body)['message']} (#{res.code})" 1395 | return 1396 | end 1397 | 1398 | # Doesn't work on 1.8.7 1399 | # more = res.header['Link'].scan(/(?<=<).*?(?=>; rel=\"next)/)[0] 1400 | more = res.header['Link'] && res.header['Link'].scan(/<[^<>]*?>; rel=\"next/)[0] 1401 | more = more && more.split('>; rel')[0][1..-1] 1402 | 1403 | VIM::command(%[normal! G"_d$]) 1404 | if more 1405 | VIM::command(%[let b:github_more_url = '#{more}']) 1406 | else 1407 | VIM::command(%[unlet b:github_more_url]) 1408 | end 1409 | 1410 | bfr = VIM::Buffer.current 1411 | result = JSON.parse(res.body) 1412 | result.each do |event| 1413 | VIM::command('let b:github_index = b:github_index + 1') 1414 | index = VIM::evaluate('b:github_index') 1415 | lines = process(prefix, event, index) 1416 | lines.each_with_index do |line, idx| 1417 | line, *links = line 1418 | links = links.map { |l| l.start_with?('/') ? prefix + l : l } 1419 | 1420 | if idx == 0 1421 | emoji = to_utf8 VIM::evaluate("s:emoji_for('#{event['type']}', 1)") 1422 | line = emoji + line 1423 | bfr.append bfr.count - 1, 1424 | "#{index.to_s.rjust(3)}) #{line} (#{format_time event['created_at']})" 1425 | else 1426 | bfr.append bfr.count - 1, VIM::evaluate('b:github_indent') + line 1427 | end 1428 | VIM::command(%[let b:github_links[#{bfr.count - 1}] = [#{links.map { |e| vstr e }.join(', ')}]]) 1429 | end 1430 | end 1431 | bfr[bfr.count] = (more && !result.empty?) ? VIM::evaluate('s:more_line') : '' 1432 | VIM::command(%[normal! ^zz]) 1433 | rescue Exception => e 1434 | error e 1435 | ensure 1436 | watcher && watcher.kill 1437 | $VERBOSE = overbose 1438 | end 1439 | 1440 | private 1441 | def process endpoint, event, idx 1442 | who = event['actor']['login'] 1443 | type = event['type'] 1444 | data = event['payload'] 1445 | where = event['url'] 1446 | action = data['action'] 1447 | repo = event['repo'] && event['repo']['name'] 1448 | 1449 | who_url = "#{endpoint}/#{who}" 1450 | repo_url = "#{endpoint}/#{repo}" 1451 | 1452 | case type 1453 | when 'CommitCommentEvent' 1454 | [[ "[#{who}] commented on commit [#{repo}@#{data['comment']['commit_id'][0, 10]}]", 1455 | who_url, data['comment']['html_url'] ]] + 1456 | wrap(data['comment']['body']).map { |e| [e] } 1457 | when 'CreateEvent' 1458 | if data['ref'] 1459 | ref_url = repo_url + "/tree/#{data['ref']}" 1460 | [["[#{who}] created #{data['ref_type']} [#{data['ref']}] at [#{repo}]", who_url, ref_url, repo_url]] 1461 | else 1462 | [["[#{who}] created #{data['ref_type']} [#{repo}]", who_url, repo_url]] 1463 | end 1464 | when 'DeleteEvent' 1465 | [["[#{who}] deleted #{data['ref_type']} #{data['ref']} at [#{repo}]", who_url, repo_url]] 1466 | when 'DownloadEvent' 1467 | # TODO 1468 | [["#{type} from [#{who}]"], who_url] 1469 | when 'FollowEvent' 1470 | whom = data['target']['login'] 1471 | [["[#{who}] started following [#{whom}]", who_url, "#{endpoint}/#{whom}"]] 1472 | when 'ForkEvent' 1473 | [["[#{who}] forked [#{repo}] to [#{data['forkee']['full_name']}]", 1474 | who_url, repo_url, data['forkee']['html_url']]] 1475 | when 'ForkApplyEvent' 1476 | # TODO 1477 | [["#{type} from [#{who}]"], who_url] 1478 | when 'GistEvent' 1479 | [["[#{who}] created a gist [#{data['gist']['html_url']}]", who_url, data['gist']['html_url']]] 1480 | when 'GollumEvent' 1481 | [["[#{who}] edited the [#{repo}]", who_url, repo_url]] + 1482 | data['pages'].map { |page| 1483 | ["Edited [#{page['title']}]", page['html_url']] 1484 | } 1485 | when 'IssueCommentEvent' 1486 | [["[#{who}] commented on issue [#{repo}##{data['issue']['number']}]", who_url, data['comment']['html_url']]] + 1487 | wrap(data['comment']['body']).map { |line| [line] } 1488 | when 'IssuesEvent' 1489 | title = emoji data['issue']['title'] 1490 | [ 1491 | ["[#{who}] #{action} issue [#{repo}##{data['issue']['number']}]", who_url, data['issue']['html_url']], 1492 | [title] 1493 | ] 1494 | when 'MemberEvent' 1495 | [["[#{who}] #{action} [#{data['member']['login']}] to [#{repo}]", who_url, data['member']['html_url'], repo_url]] 1496 | when 'PublicEvent' 1497 | [["[#{who}] open-sourced [#{repo}]", who_url, repo_url]] 1498 | when 'PullRequestEvent' 1499 | title = emoji data['pull_request']['title'] 1500 | [ 1501 | ["[#{who}] #{action} pull request [#{repo}##{data['number']}]", who_url, data['pull_request']['html_url']], 1502 | [title] 1503 | ] 1504 | when 'PullRequestReviewEvent' 1505 | prnum = data['pull_request']['url'].scan(/[0-9]+$/).first 1506 | [["[#{who}] requested your review on pull request [#{repo}##{prnum}]", who_url, data['pull_request']['html_url']]] 1507 | when 'PullRequestReviewCommentEvent' 1508 | prnum = data['comment']['pull_request_url'].scan(/[0-9]+$/).first 1509 | [["[#{who}] commented on pull request [#{repo}##{prnum}]", who_url, data['comment']['html_url']]] + 1510 | wrap(data['comment']['body']).map { |e| [e] } 1511 | when 'PushEvent' 1512 | branch = data['ref'].split('/').drop(2).join('/') 1513 | ref_url = repo_url + "/tree/#{branch}" 1514 | [["[#{who}] pushed to [#{branch}] at [#{repo}]", who_url, ref_url, repo_url]] + 1515 | data['commits'].map { |commit| 1516 | title = emoji (commit['message'].lines.first || "").chomp 1517 | ["[#{commit['sha'][0, 7]}] #{title}", repo_url + '/commit/' + commit['sha']] 1518 | } 1519 | when 'ReleaseEvent' 1520 | release_url = data['release']['html_url'] 1521 | [[ "[#{who}] released [#{data['release']['name']}] at [#{repo}]", 1522 | who_url, 1523 | release_url, 1524 | repo_url ]] + 1525 | data['release']['assets'].map { |a| a['label'] }.compact.map { |a| [a] } # No URL in API 1526 | when 'TeamAddEvent' 1527 | # TODO 1528 | [["#{type} from [#{who}]", who_url]] 1529 | when 'WatchEvent' 1530 | [["[#{who}] starred [#{repo}]", who_url, repo_url]] 1531 | else 1532 | [["#{type} from [#{who}]", who_url]] 1533 | end 1534 | end 1535 | 1536 | def wrap str 1537 | tw = VIM::evaluate("&textwidth").to_i 1538 | if tw == 0 1539 | tw = 70 1540 | else 1541 | tw = [tw - 10, 1].max 1542 | end 1543 | 1544 | emoji(str).each_line.map(&:rstrip).drop_while(&:empty?).map do |line| 1545 | line.length > tw ? 1546 | line.gsub(/(.{1,#{tw}})(\s+|$)/, "\\1\n").each_line.map(&:rstrip) : line 1547 | end.flatten 1548 | end 1549 | 1550 | def error e 1551 | VIM::command(%[let b:github_error = 1]) 1552 | VIM::command(%[echoerr #{vstr e}]) 1553 | end 1554 | 1555 | def vstr s 1556 | %["#{s.to_s.gsub '"', '\"'}"] 1557 | end 1558 | 1559 | def format_time at 1560 | time = Time.parse(at) 1561 | diff = Time.now - time 1562 | pdenom = 1 1563 | [ 1564 | [60, 'second'], 1565 | [60 * 60, 'minute'], 1566 | [60 * 60 * 24, 'hour' ], 1567 | [nil, 'day'] 1568 | ].each do |pair| 1569 | denom, unit = pair 1570 | if denom.nil? || diff < denom 1571 | t = diff.to_i / pdenom 1572 | return "#{t} #{unit}#{t == 1 ? '' : 's'} ago" 1573 | end 1574 | pdenom = denom 1575 | end 1576 | end 1577 | 1578 | def to_utf8 str 1579 | if str.respond_to?(:force_encoding) 1580 | str.force_encoding('UTF-8') 1581 | else 1582 | str 1583 | end 1584 | end 1585 | 1586 | if VIM::evaluate("s:is_mac") == 1 1587 | def emoji str 1588 | str.gsub(/:[-+0-9a-zA-Z_]+:/) { |m| 1589 | e = to_utf8 VIM::evaluate("s:emoji('#{m[1..-2]}')") 1590 | e.empty? ? m : e 1591 | } 1592 | end 1593 | else 1594 | def emoji str 1595 | str 1596 | end 1597 | end 1598 | end 1599 | end 1600 | EOF 1601 | " }}} 1602 | --------------------------------------------------------------------------------