Skip to content

Instantly share code, notes, and snippets.

@kch
Last active June 30, 2025 17:59
Show Gist options
  • Save kch/7e80148678fe42820b850fc856de0754 to your computer and use it in GitHub Desktop.
Save kch/7e80148678fe42820b850fc856de0754 to your computer and use it in GitHub Desktop.
iTerm2 file opener for semantic history
#!/opt/homebrew/opt/ruby/bin/ruby
# iTerm2 file opener for semantic history
#
# Setup: iTerm2 » Settings » Profiles » Advanced » Semantic History » Always run command…
# Command: /path/to/iterm2-edit \5 \1
#
# Parses text selections and opens appropriate handler:
# - Text files: $EDITOR with line:col positioning
# - Binary files: system default app
# - URLs: browser
#
# Path resolution priority:
# 1. Absolute paths
# 2. Relative to working directory
# 3. Relative to git root
# 4. Git diff paths (strips a/b/ prefixes as fallback)
require 'open3'
require 'shellwords'
def shell(*args) = Open3.capture2(*args).then{ |out,status| out.chomp if status.success? }
def git_root(path) = shell("git -C #{path.shellescape} rev-parse --show-toplevel")
def text_file?(path) = shell("file", path).match?(/ASCII text|UTF-8|text/)
def file_exists?(path) = File.exist?(path)
def try_file(*parts, lc: nil) = File.join(*parts).then{ [text_file?(it) ? :text : :binary, it, *lc] if file_exists?(it) }
def detect(text, cwd)
text = text.sub(/\A"(.*)"\z/, '\1') # unwrap path in "..."
text = text.sub(/\A\[(.*)\]\z/, '\1') # unwrap path in [...]
_, path, *lc = *text.match(/\A(.*?)(?::(\d+))?(?::(\d+))?\z/) # path[:line][:col]
# absolute path
return try_file(path, lc: lc) if path[0] == ?/
# relative: will look in cwd and git root for path or path with a/ b/ stripped
dirs = [cwd, git_root(cwd)].uniq.compact
paths = [path, path.sub(%r[^[ab]/], "")].uniq.compact
rel = dirs.product(paths).lazy.filter_map { try_file(*it, lc: lc) }.first
return rel if rel
# URLs
url = (protocol = text.match?(%r[^[a-z]+://])) || text.match?(/^[a-z0-9.-]+\.[a-z]{2,}/)
return [:url, protocol ? text : "http://#{text}"] if url
return nil
end
### Main
unless ARGV.empty?
working_dir, filename, = *ARGV
exit 1 unless filename
type, path, line, col = result = detect(filename, working_dir)
exit unless result
case type
when :text
# figure out editor from an interactive shell, or the fallbackest of the fallbacks
editor = shell(%[env TERM=dumb #{ENV["SHELL"]} -l -i -c 'eval "set -- $EDITOR" && command -v "$1"']) || "open -a TextEdit"
exec "#{editor} #{[path.shellescape, line, col].compact.join(":")}"
# OR JUST HARDCODE ONE OF
# exec("open", "subl://open?url=file://#{path}&line=#{line}&column=#{col}")
# exec("open", "txmt://open?url=file://#{path}&line=#{line}&column=#{col}")
# exec("open", "vscode://file/#{path}:#{line}:#{col}")
# exec("open", "zed://file/#{path}:#{line}:#{col}")
when :binary then exec("open", path)
when :url then exec("open", path)
end
exit 1
end
#############
### Tests ###
#############
def shell(*args) = raise
def text_file?(path) = path.end_with?(".rb")
def git_root(dir) = ("/repo" if dir.include?("repo"))
def file_exists?(path)
%[
/abs/path/file.rb
/no-git/file.rb
/repo/src/main.rb
/repo/image.png
/repo/working/file.rb
/repo/working/a/diff.rb
/repo/working/b/change.rb
/repo/working/stripped.rb
/repo/stripped.rb
/repo/a/prefixed.rb
/repo/b/prefixed.rb
/repo/working/spaces in name.rb
/repo/working/[brackets].rb
/repo/working/nested/deep/file.rb
].strip.split(/\s*\n\s*/).include?(path)
end
def test(a, b, msg)
return if a == b
puts "Failed: #{msg} #{[a,b].inspect} @ #{caller_locations(1, 1)[0].then{ it.path + ":" + it.lineno.to_s }}"
exit 1
end
repo = "/repo/working"
# Path resolution - basic file finding
test detect("/abs/path/file.rb", repo), [:text, "/abs/path/file.rb", nil, nil], "absolute path"
test detect("file.rb", repo), [:text, "/repo/working/file.rb", nil, nil], "relative in working dir"
test detect("src/main.rb", repo), [:text, "/repo/src/main.rb", nil, nil], "relative from git root"
test detect("nested/deep/file.rb", repo), [:text, "/repo/working/nested/deep/file.rb", nil, nil], "nested relative path"
# Git diff prefix handling - a/ and b/ paths from git output
test detect("a/diff.rb", repo), [:text, "/repo/working/a/diff.rb", nil, nil], "a/ found as-is in working dir"
test detect("b/change.rb", repo), [:text, "/repo/working/b/change.rb", nil, nil], "b/ found as-is in working dir"
test detect("a/stripped.rb", repo), [:text, "/repo/working/stripped.rb", nil, nil], "a/ prefix stripped fallback"
test detect("b/stripped.rb", repo), [:text, "/repo/working/stripped.rb", nil, nil], "b/ prefix stripped fallback"
test detect("a/prefixed.rb", repo), [:text, "/repo/a/prefixed.rb", nil, nil], "a/ found as-is in git root"
test detect("b/prefixed.rb", repo), [:text, "/repo/b/prefixed.rb", nil, nil], "b/ found as-is in git root"
# File types
test detect("file.rb", repo), [:text, "/repo/working/file.rb", nil, nil], "text file"
test detect("image.png", repo), [:binary, "/repo/image.png", nil, nil], "binary file"
# Line/column parsing
test detect("file.rb:123", repo), [:text, "/repo/working/file.rb", "123", nil], "file with line number"
test detect("file.rb:123:45", repo), [:text, "/repo/working/file.rb", "123", "45"], "file with line and column"
test detect("/abs/path/file.rb:10:5", repo), [:text, "/abs/path/file.rb", "10", "5"], "absolute path with line/col"
# Quoted/bracketed paths
test detect('"file.rb"', repo), [:text, "/repo/working/file.rb", nil, nil], "double quoted path"
test detect('"spaces in name.rb"', repo), [:text, "/repo/working/spaces in name.rb", nil, nil], "double quoted with spaces"
test detect('"file.rb:123:45"', repo), [:text, "/repo/working/file.rb", "123", "45"], "double quoted with line/col"
test detect("[brackets].rb", repo), [:text, "/repo/working/[brackets].rb", nil, nil], "bracketed path"
test detect("[file.rb]", repo), [:text, "/repo/working/file.rb", nil, nil], "square bracketed input"
# URLs
test detect("example.com", repo), [:url, "http://example.com"], "URL domain only"
test detect("x.com/path/to/page", repo), [:url, "http://x.com/path/to/page"], "URL with path"
test detect("https://example.com/path", repo), [:url, "https://example.com/path"], "URL with protocol"
test detect("ftp://example.com/file", repo), [:url, "ftp://example.com/file"], "URL different protocol"
test detect("sub.example.com", repo), [:url, "http://sub.example.com"], "URL with subdomain"
test detect("example.com:8080", repo), [:url, "http://example.com:8080"], "URL with port"
test detect("x.co/p?q=1", repo), [:url, "http://x.co/p?q=1"], "URL with query params"
test detect("x.co/p#section", repo), [:url, "http://x.co/p#section"], "URL with fragment"
# Edge cases
test detect("file.rb", "/no-git"), [:text, "/no-git/file.rb", nil, nil], "no git root available"
test detect("truly-nonexistent-file", repo), nil, "no match for nonexistent file"
test detect("justtext", repo), nil, "not URL - single word"
test detect("", repo), nil, "empty string"
puts "ok"
@kch
Copy link
Author

kch commented Jun 30, 2025

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment