vim-stargazer: fuzzy find and open your starred repositories from Vim
I find myself frequently navigating to GitHub repositories for the same dozen or so gems or JavaScript libraries. I thought this problem sounded like a good case for writing a vim plugin. After some brainstorming, here's what I wanted the plugin to do:
- Retrieve a list of my starred repositories from GitHub. I've never really found starring a repository on GitHub that useful, but I figured this would be a good way to collect the repositories I frequently visit and help give those stars more meaning.
- Create a vim command that would allow me to fuzzy find a starred repository and open it's page on GitHub.
- Allow the user to opt-in to navigating to the README section directly.
Generating a list of a user's starred repositories
I decided to use the octokit gem because it made working with GitHub's paginated API a breeze. I was even able to flesh out the feature using TDD with the following spec as my guide:
require_relative "../spec_helper"
require_relative '../../lib/star_fetcher'
describe StarFetcher do
it "creates a file with a user's stars" do
github_username = "anhari"
expect(StarFetcher.fetch_stars_for_user(user: github_username)).
to eq %w[
lokesh/lightbox2
jonathanslenders/ptpython
# … deleted for brevity
junegunn/vim-plug
thoughtbot/suspenders
tiimgreen/github-cheat-sheet
vsouza/awesome-ios
matteocrippa/awesome-swift
]
end
end
This spec, while not perfect because it should really mock the API request, eventually led to the following implementation:
require "octokit"
class StarFetcher
def self.fetch_stars_for_user(user:)
Octokit.auto_paginate = true
stars = Octokit.starred(user)
repository_full_names = []
stars.each do |star|
repository_full_names << star["full_name"]
end
repository_full_names
end
end
And voilà! I had a class with a singleton method capable of pulling down the
full list of a user's starred repositories. Now I just needed a way to utilize
this method from the command-line so that it could be called from within vim. I
eventually came up with the following ruby script that takes the user's GitHub
user name and generates a dotfile, ~/.starred_repositories
, in the user's
home directory:
require "octokit"
require_relative "./star_fetcher"
github_username = ARGV[0]
starred_repositories = StarFetcher.fetch_stars_for_user(
user: github_username
)
File.open("#{Dir.home}/.starred_repositories", "w") do |file|
starred_repositories.each { |star| file.puts star }
end
Luckily, Ruby provides the Dir.home command to return the home directory of the current user (or the named user if one is provided). Next, I simply needed to wrap this functionality in a Vim command:
function! FetchStars(github_username)
echo 'Fetching your starred repositories...'
execute 'silent !ruby ~/.vim/bundle/vim-stargazer/lib/fetch_star_list.rb ' . a:github_username
if !empty(glob("~/.starred_repositories"))
echo 'Fetching of your starred repositores is complete!'
else
echo 'Fetch unsuccessful.'
endif
endfunction
" ...
command! -nargs=1 FetchStars call FetchStars(<f-args>)
Implementing a fuzzy finder for the list of starred repositories
With the dotfile being generated with a list of the user's starred repositories, the last step was to build an integration with fzf that would allow a user to fuzzy find a repository to open. Thanks to fzf's excellent documentation, implementing this wasn't too bad:
function! OpenRepository(github_user_and_repository)
if g:StargazerNavigateToREADME
execute 'silent !open https:\/\/github.com\/' . a:github_user_and_repository . '\#readme'
else
execute 'silent !open https:\/\/github.com\/' . a:github_user_and_repository
endif
endfunction
function! Stargaze()
if !empty(glob("~/.starred_repositories"))
call fzf#run({
\ 'source': 'grep --line-buffered --color=never -hsi --include=.starred_repositories "" * ~/.starred_repositories',
\ 'down': '40%',
\ 'sink': function('OpenRepository')})
else
echo "Run the FetchStars <github_username> command to populate your stars!"
endif
endfunction
The Stargaze()
method passes three options to the fzf#run
method in order to
build this integration:
source
: grep's your~/.starred_repositories
dotfile and feeds the results to fzfdown
: tells tmux or neovim how to display the fuzzy finder.sink
: tells fzf what it should do with the selected entry, which in this case is passing the result to theOpenRepository
function defined above.
Finding a way to deal with multiple README extensions
You may have noticed the global Vim variable StargazerNavigateToREADME
in the
OpenRepository
Vim function shown above. If this option is enabled the browser
will automatically navigate to the #readme
css selector on the repository's
homepage. I realized that navigating to this css elector was much easier than
trying to navigate directly to a README blob with an unpredictable extension.
I decided to disable this option by default, and to rename the plugin from
vim-readme to vim-stargazer in order to better capture it's functionality.
Conclusion
I am really happy with how the plugin turned out. The process forced me to try a few things I'd never had before like building a vim plugin that leveraged Ruby and interacting with GitHub's API. I'm chalking it up as a win.