Skip to content

Instantly share code, notes, and snippets.

@sandrods
Last active April 24, 2025 15:16
Show Gist options
  • Save sandrods/e238f5563d6f7ec1a5f0def1f3ed7d21 to your computer and use it in GitHub Desktop.
Save sandrods/e238f5563d6f7ec1a5f0def1f3ed7d21 to your computer and use it in GitHub Desktop.
Focus: A Developer's Context Switcher

Focus: A Developer's Context Switcher

When working on large codebases, developers often need to switch between different features or areas of the application. The focus script helps manage this cognitive load by creating organized workspaces for different contexts.

The Problem

Working on features that span multiple directories (controllers, models, views, specs) requires constant navigation and context switching. This leads to keeping multiple editor windows open, maintaining mental lists of relevant files, and repeatedly searching through deep directory structures.

The Solution

The focus script creates virtual workspaces using symlinks. It maintains a configuration file where you define groups of related files and directories. When you switch to a focus area, the script creates a dedicated directory with symlinks to all relevant files, preserving their original structure while making them easily accessible in one place.

Features

1. Easy Setup

  • First-time run creates an example configuration
  • Opens the config file in your editor for immediate customization

2. Smart Symlink Management

  • Creates directory structure as needed
  • Handles both files and directories
  • Maintains original file structure within the focus directory

3. Refresh Mode

When you update your configuration, you can refresh your focus area without losing existing symlinks:

bin/focus invoices --refresh

Usage

Configuration

Define your focus areas in .focus/config.yml:

invoices:
  - app/assets/stylesheets/components.css
  - app/components/invoices
  - app/components/ui/picker
  - app/controllers/invoices_controller.rb
  - app/controllers/invoices
  - app/controllers/picker
  - app/models/invoice.rb
  - app/models/invoice_item.rb
  - app/views/invoices
  - app/views/picker

Basic Commands

# First time setup
bin/focus

# Switch to a focus area
bin/focus invoices

# Update after config changes
bin/focus invoices --refresh

Benefits

  1. Reduced Cognitive Load: All relevant files for a feature are in one place
  2. Faster Navigation: No need to dig through deep directory structures
  3. Non-Destructive: Uses symlinks, so original files remain unchanged
  4. Flexible: Easy to update and modify focus areas as needed

Installation

  1. Copy the focus script to your project's bin directory
  2. Make it executable: chmod +x bin/focus

Tips

  • Include both implementation and test files in your focus areas
  • Use directories when you need all files in a folder
  • Use --refresh when you want to preserve existing links

The focus script is a simple tool that can significantly improve your development workflow by reducing the friction of context switching and keeping related files easily accessible.

#!/usr/bin/env ruby
require "fileutils"
require "yaml"
FOCUS_FILE = ".focus/config.yml"
FOCUS_ROOT = ".focus"
EDITOR_COMMAND = "cursor" # Change to "code" or whatever
def list_available_sets(sets)
puts "ℹ️ Available focus sets:"
sets.keys.each { |key| puts " - #{key}" }
end
def create_example_focus_file
example = {
"invoices" => [
"app/controllers/invoices_controller.rb",
"app/models/invoice.rb",
"app/views/invoices",
"spec/models/invoice_spec.rb"
],
"auth" => [
"app/controllers/sessions_controller.rb",
"app/models/user.rb",
"app/views/sessions",
"spec/requests/login_spec.rb"
]
}
FileUtils.mkdir_p(File.dirname(FOCUS_FILE))
File.write(FOCUS_FILE, example.to_yaml)
puts "✅ Created #{FOCUS_FILE} with example sections."
end
unless File.exist?(FOCUS_FILE)
puts "⚠️ #{FOCUS_FILE} not found."
print "Would you like to create a sample one? (Y/n): "
answer = $stdin.gets.strip.downcase
if answer.empty? || answer == "y" || answer == "yes"
create_example_focus_file
system(EDITOR_COMMAND, FOCUS_FILE)
exit 0
else
puts "🚫 Aborted. Please create #{FOCUS_FILE} manually."
exit 1
end
end
begin
sets = YAML.load_file(FOCUS_FILE)
rescue Psych::SyntaxError => e
puts "❌ YAML parsing error in #{FOCUS_FILE}:\n#{e.message}"
exit 1
end
if ARGV.empty?
puts "Usage: bin/focus [section_name] [--refresh]"
list_available_sets(sets)
exit 0
end
refresh_mode = ARGV.include?("--refresh")
section = ARGV.find { |arg| !arg.start_with?("--") }
unless section
puts "❌ Please provide a section name"
list_available_sets(sets)
exit 1
end
unless sets.key?(section)
puts "❌ Section '#{section}' not found in #{FOCUS_FILE}."
list_available_sets(sets)
exit 1
end
target_dir = File.join(FOCUS_ROOT, section)
puts "🔄 #{refresh_mode ? "Refreshing" : "Creating"} symlinks in #{target_dir}/ ..."
# Only remove existing symlinks if not in refresh mode
FileUtils.rm_rf(target_dir) unless refresh_mode
sets[section].each do |relative_path|
source_path = File.expand_path(relative_path)
dest_path = File.join(target_dir, relative_path)
if File.exist?(source_path)
begin
FileUtils.mkdir_p(File.dirname(dest_path))
FileUtils.ln_s(source_path, dest_path) unless File.exist?(dest_path)
puts "✅ Linked: #{relative_path}"
rescue Errno::EEXIST
puts "ℹ️ Already exists: #{relative_path}"
end
else
puts "❌ Missing: #{relative_path}"
end
end
puts "\n📂 Focus folder ready: #{target_dir}"
puts "🚀 Opening with: #{EDITOR_COMMAND} #{target_dir}/"
system(EDITOR_COMMAND, target_dir)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment