#!/usr/bin/env python import subprocess import os import sys import re from urllib.parse import urlsplit extract_line_regex = re.compile(r"(.*?)(?::([0-9]+))?(?::([0-9]+))?(:)?$") main_editor = "nvim" editors = [main_editor, "vim", "vi"] real_editor = os.environ.get("REAL_EDITOR", None) editor_command = main_editor if real_editor is not None: editors.append(real_editor) editor_command = real_editor def run(cmd, only_stdout=True): res = subprocess.run(cmd, capture_output=True, text=True, shell=True) if only_stdout: return str(res.stdout) else: return res def find_pane(window): for editor in editors: pane = run(f"tmux list-panes -a -f '#{{&&:#{{==:#{{window_id}},{window}}},#{{==:#{{pane_current_command}},{editor}}}}}' -F '#{{pane_id}}'").strip(); if pane != "": return pane def create_pane(args): run(f'tmux split-window -h -P -F "#{{pane_id}}" {editor_command} {args}') def find_or_create_pane(window, args): if pane := find_pane(window): # exit copy mode so we don't send these commands directly to tmux run(f"tmux send-keys -t {pane} -X cancel") # Escape for some reason doesn't get sent as the escape key if it shows up next to any other keys??? run(f"tmux send-keys -t {pane} Escape") # note the space, this tells nvim not to save it in history run(f"tmux send-keys -t {pane} \": drop {args}\" Enter") run(f"tmux select-pane -t {pane} -Z") else: create_pane(args) def split_line(filename): mtch = extract_line_regex.match(filename) file = mtch.group(1) line = mtch.group(2) column = mtch.group(3) url = urlsplit(line) if url.scheme == "file" and url.path != file: file = url.path file = os.path.abspath(file) print(f"opening {file}:{line}:{column}") return file, line, column def join_line(filename, maybe_line, maybe_column): assert not (maybe_line is None and maybe_column is not None) if maybe_line is None: return filename if maybe_column is None: maybe_column = "0" return f"+normal!{maybe_line}G{maybe_column} {filename}" def trim(arg): if arg.startswith("\"") and not arg.endswith("\""): arg = arg.lstrip("\"") if arg.endswith("\"") and not arg.startswith("\""): arg = arg.rstrip("\"") return arg.strip() def editor_hax(filename): args = split_line(trim(filename)) current_window = run("tmux display-message -p \"#{window_id}\"").strip(); find_or_create_pane(current_window, join_line(*args)) def xdg_open(arg): subprocess.run(f"xdg-open {trim(arg)}", shell=True) def xdg_open_proxy(*args): for arg in args: try: _file, line, _column = split_line(arg) if line is not None: editor_hax(arg) else: xdg_open(arg) except: xdg_open(arg) def usage(): print(f"usage: {sys.argv[0]} filename:(line):(column)") print(" Opens a file with line number and colum in the editor in another pane of the current tmux window") print(f"usage: {sys.argv[0]} xdg-open-proxy filename:(line):(column)") print(" Calls xdg-open on the given parameters. However, if any part contains a line number and or column, opens in nvim like editor-hax") exit(1) if __name__ == "__main__": if len(sys.argv) < 2: usage() elif sys.argv[1] == "xdg-open-proxy": if len(sys.argv) < 3: usage() else: xdg_open_proxy(*sys.argv[1:]) elif len(sys.argv) > 2: usage() else: editor_hax(sys.argv[1])