Skip to content

Instantly share code, notes, and snippets.

@nakwa
Last active December 9, 2025 15:44
Show Gist options
  • Select an option

  • Save nakwa/b00ae372174981bf5fce673ef64ca75d to your computer and use it in GitHub Desktop.

Select an option

Save nakwa/b00ae372174981bf5fce673ef64ca75d to your computer and use it in GitHub Desktop.
Ask Claude Code - Simple Sublime Text Plugin for Claude Code
"""
Ask Claude - Sublime Text Plugin
This plugin integrates Claude Code CLI with Sublime Text, allowing you to send code
selections directly to Claude for analysis, refactoring, debugging, or any other task.
INSTALLATION:
-------------
1. Save this file as 'ask_claude.py' in your Sublime Text User package directory:
macOS: ~/Library/Application Support/Sublime Text 3/Packages/User/ask_claude.py
Quick access: Sublime Text > Preferences > Browse Packages > User folder
2. Restart Sublime Text or run "Plugin: Reload Plugin" from Command Palette
REQUIREMENTS:
-------------
1. Terminus plugin for Sublime Text (https://packagecontrol.io/packages/Terminus)
Install via Package Control: Command Palette > Package Control: Install Package > Terminus
2. Claude Code CLI (https://claude.com/claude-code)
Install via: npm install -g claude-code
KEYBOARD SHORTCUTS:
-------------------
Add these to your Sublime keymap file (Preferences > Key Bindings):
{"keys": ["command+shift+c"], "command": "ask_claude", "args": {"resume": false}}
{"keys": ["command+shift+r"], "command": "ask_claude", "args": {"resume": true}}
{"keys": ["shift+enter"], "command": "terminus_keypress", "args": {"key": "j", "ctrl": true}, "context": [{"key": "terminus_view"}]}
USAGE:
------
1. Cmd+Shift+C: Start NEW Claude session
- Opens Claude in a new Terminus panel (creates split view if needed)
- Sends selected code with file path and line numbers
- If no text is selected, sends just the file path
- Creates a dedicated Claude context directory per project (~/.sublime-claude/{project-id}/)
2. Cmd+Shift+R: RESUME existing Claude session
- Continues your existing conversation with Claude
- Automatically finds and reuses the Claude Terminus view if already open
- Maintains conversation history and context
- Ideal for iterative development workflows
HOW IT WORKS:
-------------
- Automatically creates a project-specific Claude context directory for each Sublime project
- Grants Claude access to your project folders via additionalDirectories in settings.json
- Auto-paste file path, lines and code selections into Claude
- Opens Claude in an opposite pane for side-by-side workflow
- Preserves conversation history when resuming sessions
import sublime
import sublime_plugin
import uuid
class AskClaudeCommand(sublime_plugin.TextCommand):
"""Send selected text or current line to Claude in Terminus"""
def run(self, edit, resume=True):
# Get the current file path
file_path = self.view.file_name() or "untitled"
# Get file extension for syntax highlighting
import os
file_ext = ""
if file_path and file_path != "untitled":
_, ext = os.path.splitext(file_path)
file_ext = ext[1:] if ext else "" # Remove the dot
# Get selected text
selection = self.view.sel()
selected_text = ""
line_info = ""
# Check if there's any actual text selected
has_selection = False
selected_regions = []
for region in selection:
if not region.empty():
has_selection = True
selected_regions.append(region)
if has_selection and len(selected_regions) > 0:
# Build message with selected text
regions_text = []
for region in selected_regions:
regions_text.append(self.view.substr(region))
selected_text = "\n".join(regions_text)
# Get line numbers of first selection
first_line = self.view.rowcol(selected_regions[0].begin())[0] + 1
last_line = self.view.rowcol(selected_regions[-1].end())[0] + 1
if first_line == last_line:
line_info = "Line {}".format(first_line)
else:
line_info = "Lines {}-{}".format(first_line, last_line)
# Remove trailing empty lines and add indentation to each line of code
selected_text = selected_text.rstrip('\n')
indented_text = "\n".join(" " + line for line in selected_text.split("\n"))
# Format the message with code fence for syntax highlighting
message = "# {}\n# {}\n\n```{}\n{}\n```\n\n".format(
file_path,
line_info,
file_ext,
indented_text
)
else:
# No selection - only send file path
message = "# {}\n\n".format(file_path)
# Check if there's already a Terminus view with Claude running
terminus_view = self._find_claude_terminus_view()
if terminus_view:
print("AskClaude: Found existing terminus view: {}".format(terminus_view.name()))
# Send text to existing Terminus
self._send_to_terminus(terminus_view, message)
else:
print("AskClaude: No existing terminus view found, opening new one")
# Open new Terminus with Claude and send message after delay
self._open_new_terminus(message, resume=resume)
def _find_claude_terminus_view(self):
"""Find an existing Terminus view running Claude"""
window = self.view.window()
if not window:
return None
for view in window.views():
# Check if it's a Terminus view
if view.settings().get('terminus_view'):
# Check if it has 'claude' in the title or command
view_name = view.name() or ""
# Also check the tag which might contain the command
tag = view.settings().get('terminus_view.tag') or ""
cmd = view.settings().get('terminus_view.cmd') or ""
if 'claude' in view_name.lower() or 'claude' in tag.lower() or 'claude' in str(cmd).lower():
return view
return None
def _send_to_terminus(self, terminus_view, text):
"""Send text to an existing Terminus view"""
window = self.view.window()
if window:
print("AskClaude: Focusing terminus view")
# Focus the Terminus view
window.focus_view(terminus_view)
# Use sublime.set_timeout to ensure the view is focused before sending
def send_text():
print("AskClaude: Clearing input buffer with Ctrl+C keypress")
# Send Ctrl+C as an actual keypress to clear the input buffer
terminus_view.run_command('terminus_keypress', {
'key': 'c',
'ctrl': True
})
# Small delay before sending the actual text
def send_actual_text():
print("AskClaude: Sending text to terminus: {}".format(repr(text[:50])))
window.run_command('terminus_send_string', {
'string': text,
'visible_only': True
})
sublime.set_timeout(send_actual_text, 200)
sublime.set_timeout(send_text, 100)
def _get_project_session_id(self):
"""Generate a deterministic session ID based on the project path"""
window = self.view.window()
if not window:
return None
# Get the project folder
folders = window.folders()
if folders:
project_path = folders[0]
else:
# Use current file directory if no project
if self.view.file_name():
import os
project_path = os.path.dirname(self.view.file_name())
else:
return None
# Generate a deterministic UUID from the project path
# Use UUID5 with a namespace (DNS namespace is fine)
namespace = uuid.UUID('6ba7b810-9dad-11d1-80b4-00c04fd430c8') # DNS namespace
session_uuid = uuid.uuid5(namespace, project_path)
return str(session_uuid)
def _setup_project_claude_context(self):
"""Setup a dedicated Claude context directory for this project"""
import os
import json
window = self.view.window()
if not window:
return None, False
# Get project session ID
session_id = self._get_project_session_id()
if not session_id:
return None, False
# Create dedicated directory for this Sublime project's Claude context
claude_sublime_dir = os.path.expanduser('~/.sublime-claude')
project_claude_dir = os.path.join(claude_sublime_dir, session_id)
claude_settings_dir = os.path.join(project_claude_dir, '.claude')
# Create directories if they don't exist
os.makedirs(claude_settings_dir, exist_ok=True)
# Get all project folders for additionalDirectories
project_folders = window.folders()
if not project_folders and self.view.file_name():
# If no project folders, use the current file's directory
project_folders = [os.path.dirname(self.view.file_name())]
# Generate Claude settings.json with project access
settings = {
"additionalDirectories": project_folders
}
settings_path = os.path.join(claude_settings_dir, 'settings.json')
with open(settings_path, 'w') as f:
json.dump(settings, f, indent=2)
print("AskClaude: Created Claude context at {}".format(project_claude_dir))
print("AskClaude: Granted access to: {}".format(project_folders))
return project_claude_dir
def _open_new_terminus(self, initial_text, resume=True):
"""Open a new Terminus window with Claude in opposite pane"""
window = self.view.window()
if not window:
return
# Setup project-specific Claude context directory
claude_context_dir = self._setup_project_claude_context()
if not claude_context_dir:
print("AskClaude: Failed to setup Claude context, using default")
claude_context_dir = window.folders()[0] if window.folders() else None
# Get current group and determine target group for terminus
current_group = window.active_group()
num_groups = window.num_groups()
# If only one group, create a split layout (2 columns)
if num_groups == 1:
window.run_command('set_layout', {
'cols': [0.0, 0.5, 1.0],
'rows': [0.0, 1.0],
'cells': [[0, 0, 1, 1], [1, 0, 2, 1]]
})
target_group = 1 # Open in the new right pane
else:
# Use the opposite group (if in 0, use 1; if in 1, use 0)
target_group = 1 if current_group == 0 else 0
# Focus the target group before opening terminus
window.focus_group(target_group)
# Build Claude command
claude_cmd = 'claude --resume' if resume else 'claude'
print("AskClaude: Starting Claude with: {}".format(claude_cmd))
# Open Terminus with Claude from the project-specific context directory
window.run_command('terminus_open', {
'cmd': ['/bin/zsh', '-i', '-c', claude_cmd],
'cwd': claude_context_dir,
'title': 'Claude'
})
# Only paste prompt for new sessions, not when resuming
if not resume:
def send_delayed_text():
print("AskClaude: Pasting prompt to new session")
window.run_command('terminus_send_string', {
'string': initial_text,
'visible_only': True
})
# Wait 2 seconds for Claude to fully start up
sublime.set_timeout(send_delayed_text, 2000)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment