Rubyscope

Using Rubyscope

Rubyscope is a desktop app for reading Ruby and Rails code. Hold Ctrl and click a name to go to its definition. This page lists everything you can do with it.

On macOS, use Cmd where this page says Ctrl. Three keys stay on Ctrl: Ctrl Tab, because Cmd+Tab switches apps, and Ctrl D and Ctrl U, as in Vim.

Install

You need Linux or macOS, Rust 1.88 or newer, and clang with libclang. The Prism parser is compiled from C when you build.

Linux

git clone https://github.com/equivalent/rubyscope
cd rubyscope
bin/install

This builds the release binaries and installs rubyscope and rubyscope-cli in ~/.local/bin, an app launcher entry and the icon. Make sure ~/.local/bin is on your PATH.

macOS

bin/install-macos

This installs ~/Applications/rubyscope.app, which you can open from Spotlight or the Dock, and a link to it in ~/.local/bin. An app started from the Dock gets only the system PATH, so Rubyscope reads the PATH of your login shell when it starts.

Optional tools

ToolWhat it gives
rubyGems and the standard library in the index. Rubyscope asks mise or rbenv for the Ruby of the project, else it uses the ruby on PATH.
bundle installClicks that go into gem source. Only installed gems are indexed.
gitGit blame and "Open on GitHub".
ghA click on a blame block opens the pull request of the commit, not only the commit.
fc-matchLinux: the system monospace font. macOS uses Menlo.
OmarchyColors from the current Omarchy theme. Without it, the colors are Tokyo Night.

Open a project

rubyscope ~/code/shop                             # open a project folder
rubyscope ~/code/shop/app/models/order.rb:40      # open a file at line 40
rubyscope                                         # the current folder, or the start screen
  • With a file and no folder, the project is the nearest parent folder that has .git or a Gemfile.
  • With no arguments, Rubyscope opens the current folder when it has .git or a Gemfile. Otherwise it shows the start screen, with a path field, a "Browse…" button and your recent projects.
  • Ctrl O opens the system folder dialog. When a project is already open, the new one opens in a new window.
  • Rubyscope remembers the open tabs of each project and opens them again next time.

First steps

  1. Open a project. The index builds in the background, and the top bar shows "indexing…" and then the number of files. The first index of a large app takes a few seconds. The next start takes about one second.
  2. Open a file. Press Ctrl P and type part of the path, or click it in the file tree. Press Ctrl T to find a class, module, method or constant by name.
  3. Hold Ctrl. The names you can click get an underline. Click one to go to its definition. When there are several, Rubyscope shows a list. When a class is opened in many files, it goes to the main file and tells you how many there are.
  4. Go back with the mouse back button or the back button in the top bar. The forward button goes forward again.
  5. Right-click a name and choose "Find usages". The results panel opens at the bottom. For a method, the "callers" button on a row shows who calls the method around that row.
  6. Rest the pointer on a name for half a second. A popup shows the signature, the docs, where it is defined, the type Rubyscope found and how sure it is.
  7. Press Ctrl B to show git blame. Newer commits get a stronger blue. Click a blame line to open the commit, or its pull request, on GitHub.
  8. Press Ctrl K to see every command.
The hover popup over order.ship! with the method, its file and line, and its docs.
The hover popup on a method call. "inferred" means Rubyscope worked out the type of order from the code around it.

The window

PartWhat it has
Top barBack and forward, the project name, the file path, the breadcrumb, the "Commands" button and the index status. The breadcrumb shows the class and method at the cursor. Click a part to go there.
Side bar"Files" is the file tree. "Outline" lists the classes, modules, methods and constants of the open file.
TabsOne tab for each open file. Middle-click a tab to close it. Right-click it for more.
Action barOnly in a view file: a link to its controller action or mailer method, for example OrdersController#show. It does not scroll with the code.
CodeDrag to select text. Double-click selects a word. Ctrl C copies.
Results panelUsages, search results and the type hierarchy. The type hierarchy shows the ancestors in method lookup order, and the subclasses and includers.
A view file with the action bar above the code, linking to OrdersController#show.
The action bar above a view.

Mouse

MouseWhat it does
Ctrl + clickGo to the definition, or a list when there are several.
Ctrl + click a name where it is defined, such as class Order or PREFIX = "x"List the usages of that class, module or constant.
Ctrl + click an action nameGo to its view, a template or a Phlex view.
Click the bar above a viewGo to its controller action or mailer method.
Rest the pointer on a nameA popup with the signature, docs, location and type.
Right-click a nameA menu: go to view or go to controller action, go to definition, all definitions, find usages, type hierarchy, copy name, copy path:line, open in editor, open on GitHub.
Right-click a tab or a fileCopy its path or its full path. On a tab, also close it, the tabs to its left or right, or all tabs.
Back and forward buttonsGo back and forward. The arrows in the top bar do the same.
Click a breadcrumb partGo to that class or method.
Click a blame blockOpen the commit, or its pull request, on GitHub.
"callers" in the resultsWho calls that method, one level up.
Middle-click a tabClose the tab.
The right-click menu in a view, with Go to controller action at the top.
The right-click menu in a view starts with "Go to controller action".

Keys

KeyWhat it does
Ctrl OOpen a project folder. A new window when a project is open.
Ctrl PGo to file
Ctrl TGo to symbol: a class, module, method or constant
Ctrl Shift FSearch in all files of the project. Plain text, case is ignored.
Ctrl FFind in this file. Enter goes to the next match, Shift Enter to the previous one.
Ctrl KAll commands
Ctrl GGo to line
Ctrl WClose the tab
Ctrl TabNext tab
Ctrl Shift TabPrevious tab
Ctrl BShow or hide git blame
Ctrl Shift BShow or hide the side bar
Ctrl QClose the project and go back to the project list. Press it again to quit.
EscClose a popup, then the find bar, then the results panel

Ctrl F and Ctrl Shift F start with the selected text when it is on one line.

The go to file list, filtered by orders show.
Ctrl P, go to file
The go to symbol list, filtered by ship.
Ctrl T, go to symbol

Scroll keys

These keys scroll the code when no text field has focus.

KeyWhat it does
↑ ↓ or j kScroll three lines
← →Scroll sideways
Page Up Page DownScroll one page
Space or Shift SpaceOne page down or up
Ctrl D or Ctrl UHalf a page down or up
gg or HomeGo to the top
Shift G or EndGo to the end
A number, then gGo to that line. 500g goes to line 500.
0Scroll to the left edge

The mouse wheel scrolls three times faster than usual in egui apps. Hold Shift to scroll a page at a time.

Commands without a key

These are in the Ctrl K list. They have no key, but you can give them one.

CommandName in keys.toml
Backback
Forwardforward
Find usages of the symbol at the cursorfind_usages
Go to the view of the action, or the action of the viewgo_to_view
Type hierarchy of the class at the cursortype_hierarchy
Outline of this filefile_outline
Show or hide the results paneltoggle_bottom_panel
Open in editor at this lineopen_in_editor
Open this line on GitHubopen_on_github
Copy path:linecopy_path
Index the project again, parsing every filereindex

"Open in editor" uses omarchy-launch-editor when it is on PATH. Otherwise it opens nvim in a terminal. On macOS it opens nvim in a new Terminal window. "Open on GitHub" needs an origin remote on github.com and opens the line at the commit of HEAD.

Change the keys

Put your keys in ~/.config/rubyscope/keys.toml. A key in this file replaces the default key of that command. The other commands keep their defaults. Rubyscope reads the file when it starts.

toggle_blame = "cmd+alt+b"
find_usages = "alt+u"
back = "alt+left"
forward = "alt+right"
reindex = "f5"

A key is modifiers and one key, joined with +. The modifiers are cmd, ctrl, shift and alt. cmd is Cmd on macOS and Ctrl on Linux, so one file works on both. ctrl is always the Ctrl key. The key can be a letter, a digit, esc, tab, enter, space, an arrow (left, right, up, down) or f1 to f12.

Names of the commands that have a default key
NameDefault
open_folderCtrl O
go_to_fileCtrl P
go_to_symbolCtrl T
search_projectCtrl Shift F
find_in_fileCtrl F
commandsCtrl K
go_to_lineCtrl G
close_tabCtrl W
next_tabCtrl Tab
prev_tabCtrl Shift Tab
toggle_blameCtrl B
toggle_sidebarCtrl Shift B
close_popupEsc

What a click understands

  • Constants. Ruby's constant lookup through lexical scopes, ancestors and the top level. Compact names (class A::B), constant aliases and Zeitwerk folder namespaces. A click on a namespace lists the classes in it.
  • Methods. Ruby's lookup order: prepends, the class, includes, the superclass. Class methods, class << self, extend, module_function, concerns (class_methods do, included do), class_eval, ActiveSupport.on_load and super.
  • Rails DSLs. Associations, scopes, enums, delegate, class_attribute and mattr_*, attr_*, attribute, store_accessor, attachments, alias_attribute, and columns from db/schema.rb.
  • Symbols that name methods. before_action :load, validate :check, if: :paid?, send(:x).
  • Variable types. Model.find, relations and scopes, associations, block parameters, instance variables, return values, YARD @return, FactoryBot create(:x), and as a last guess the variable's name. The hover shows how sure each answer is: exact, inferred, guess or name only.
  • Phlex. Kit calls such as Button(...) and UI::Button(...) go to UI::Button, also from a module that a class with the kit includes.
  • Controllers and views. OrdersController#edit and app/views/orders/edit.* both ways, variant folders, and Views::Orders::Edit. Mailer methods and their templates the same way.
  • Routes. Route helpers such as admin_order_path go to the route line. In routes files, resources :x goes to the controller and to: "a#b" to the action.
  • Translations. t("a.b") and lazy t(".x") in controllers, Phlex and templates go to the locale YAML line, for every locale.
  • Partials. render "x/y" goes to the partial. In ERB templates, the Ruby inside the tags works like any Ruby.
  • Tests. RSpec let and subject, described_class, shared examples, methods in describe blocks, FactoryBot factories and traits.
  • Stimulus. data-controller and data-action, in ERB and in Phlex hashes, go to the controller file and method.
  • Gems. Gems from Gemfile.lock and the Ruby standard library.
A list titled Choose a definition of ship!, with Order#ship! and GiftCard#ship!.
When the receiver is not known, Rubyscope lists every method with that name.

The saved index

Rubyscope lists the project files and respects .gitignore. It reads Gemfile.lock and asks the project's Ruby where the gems and the standard library are. It parses every Ruby file with Prism on all CPU cores, joins the files into one table, and adds the Rails, RSpec, FactoryBot and Stimulus rules.

The result is saved in ~/.cache/rubyscope/, or in $XDG_CACHE_HOME/rubyscope/. At the next start Rubyscope parses only the files whose size or change time is different. For a large Rails app with its gems, the index is ready in less than a second with the saved index, and in a few seconds without it.

While the window is open, a file watcher sees changes in app/, config/, db/, lib/, spec/, test/, Gemfile and Gemfile.lock. Open tabs load the new text and the index builds again from the changed files.

The saved index is not used after a new build of Rubyscope with new parse rules, or when config/initializers/inflections.rb changed. "Index the project again" in Ctrl K parses every file and writes a new saved index.

Limits

  • Methods with names that exist only at runtime, such as define_method with a variable name, method_missing or class_eval with a string, cannot be found by reading the code. For these, Rubyscope lists the methods with that name.
  • Some variable types are guesses. The hover always says when a type is a guess.
  • RBS signatures for core classes are not read yet. "x".strip gives a name-only list, because String is written in C.
  • Custom Zeitwerk setups are read from the initializers only.
  • Files outside the watched folders do not update the index while the window is open. Use "Index the project again".

Files Rubyscope writes

Rubyscope never writes into the project folder.

PathWhat it holds
~/.cache/rubyscope/The saved index of each project
~/.local/state/rubyscope/recent.jsonThe recent projects, up to twelve
~/.local/state/rubyscope/session*.jsonThe open tabs of each project

It reads ~/.config/rubyscope/keys.toml for keys and the current Omarchy theme for colors.

Environment variables

VariableWhat it does
XDG_CACHE_HOMEThe folder for the saved index. It must be an absolute path.
RUBYSCOPE_APP_IDThe app id of the window, which window manager rules match. The default is rubyscope.

Troubleshooting

ProblemWhat to do
A click goes to an old line, or a new method is not foundPress Ctrl K and choose "Index the project again". If that does not help, close Rubyscope and delete ~/.cache/rubyscope/.
A click on a gem or a Rails method does nothingRun ruby -v and bundle install in the project folder. Only gems installed for the project's Ruby are indexed.
Blame does not showThe project must be a git repository, and git must be on PATH.
"This project has no GitHub remote"The origin remote must be on github.com.
A blame click opens the commit, not the pull requestInstall the GitHub CLI, gh, and log in.
You want to see what the index doesStart Rubyscope from a terminal. It prints a line such as indexed 9000 files in 0.71 s.

Command-line tool

rubyscope-cli builds the same index as the window and answers questions about it. Use it to test the index or to script a lookup. Positions are FILE:LINE:COL, starting at 1.

rubyscope-cli -r ~/code/shop index                         # build the index, print counts and times
rubyscope-cli -r ~/code/shop def app/models/order.rb:12:5  # where the name goes
rubyscope-cli -r ~/code/shop refs app/models/order.rb:12:5 # every usage, the first 40
rubyscope-cli -r ~/code/shop hover app/models/order.rb:12:5
rubyscope-cli -r ~/code/shop related app/controllers/orders_controller.rb:8:7
rubyscope-cli -r ~/code/shop outline app/models/order.rb
rubyscope-cli -r ~/code/shop search "ready_to_ship"
rubyscope-cli -r ~/code/shop search --regex "def (create|update)"
rubyscope-cli -r ~/code/shop ancestors ApplicationRecord
OptionWhat it does
-r, --root FOLDERThe project folder. The default is the current folder.
--no-gemsDo not index gems
--no-stdlibDo not index the Ruby standard library
--no-cacheDo not read or write the saved index

To compare Rubyscope with a booted Rails app, run probe/rails_probe.rb inside the app with bin/rails runner, then rubyscope-cli check on the JSON file it writes. See the README for details.