|
@@ -43,7 +43,7 @@ def _has_fd():
|
|
|
def find_git_repos(dev_dir, no_cache=False):
|
|
def find_git_repos(dev_dir, no_cache=False):
|
|
|
cache_file, _ = get_cache_files(dev_dir)
|
|
cache_file, _ = get_cache_files(dev_dir)
|
|
|
debug("find_git_repos: checking cache...")
|
|
debug("find_git_repos: checking cache...")
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
if not no_cache and cache_file.exists():
|
|
if not no_cache and cache_file.exists():
|
|
|
cache_age = time.time() - os.path.getmtime(cache_file)
|
|
cache_age = time.time() - os.path.getmtime(cache_file)
|
|
|
debug(f"Cache age: {cache_age:.0f}s (max: {CACHE_MAX_AGE}s)")
|
|
debug(f"Cache age: {cache_age:.0f}s (max: {CACHE_MAX_AGE}s)")
|
|
@@ -51,28 +51,28 @@ def find_git_repos(dev_dir, no_cache=False):
|
|
|
debug("Using cached repos")
|
|
debug("Using cached repos")
|
|
|
return [r for r in cache_file.read_text().strip().split("\n") if r]
|
|
return [r for r in cache_file.read_text().strip().split("\n") if r]
|
|
|
debug("Cache expired, rescanning...")
|
|
debug("Cache expired, rescanning...")
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
debug(f"Scanning for git repositories in {dev_dir}...")
|
|
debug(f"Scanning for git repositories in {dev_dir}...")
|
|
|
if not Path(dev_dir).exists():
|
|
if not Path(dev_dir).exists():
|
|
|
return []
|
|
return []
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
repos = []
|
|
repos = []
|
|
|
if _has_fd():
|
|
if _has_fd():
|
|
|
debug("Using fd for scanning")
|
|
debug("Using fd for scanning")
|
|
|
result = subprocess.run(
|
|
result = subprocess.run(
|
|
|
- ["fd", "-H", "-t", "d", "^\.git$", dev_dir, "-d", "3", "-0"],
|
|
|
|
|
|
|
+ ["fd", "-H", "-t", "d", "^\.git$", dev_dir, "-d", "4", "-0"],
|
|
|
capture_output=True, text=True,
|
|
capture_output=True, text=True,
|
|
|
)
|
|
)
|
|
|
else:
|
|
else:
|
|
|
debug("Using find for scanning")
|
|
debug("Using find for scanning")
|
|
|
result = subprocess.run(
|
|
result = subprocess.run(
|
|
|
- ["find", dev_dir, "-maxdepth", "3", "-type", "d", "-name", ".git", "-print0"],
|
|
|
|
|
|
|
+ ["find", dev_dir, "-maxdepth", "4", "-type", "d", "-name", ".git", "-print0"],
|
|
|
capture_output=True, text=True,
|
|
capture_output=True, text=True,
|
|
|
)
|
|
)
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
if result.returncode == 0:
|
|
if result.returncode == 0:
|
|
|
repos = [str(Path(g).parent) for g in result.stdout.strip("\0").split("\0") if g]
|
|
repos = [str(Path(g).parent) for g in result.stdout.strip("\0").split("\0") if g]
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
repos = sorted(set(repos))
|
|
repos = sorted(set(repos))
|
|
|
cache_file.parent.mkdir(parents=True, exist_ok=True)
|
|
cache_file.parent.mkdir(parents=True, exist_ok=True)
|
|
|
cache_file.write_text("\n".join(repos))
|
|
cache_file.write_text("\n".join(repos))
|
|
@@ -102,10 +102,10 @@ def sort_by_mru(display_list, dev_dir):
|
|
|
mru_set = set()
|
|
mru_set = set()
|
|
|
if mru_file.exists():
|
|
if mru_file.exists():
|
|
|
mru_set = {l.strip() for l in mru_file.read_text().split("\n") if l.strip()}
|
|
mru_set = {l.strip() for l in mru_file.read_text().split("\n") if l.strip()}
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
if not mru_set:
|
|
if not mru_set:
|
|
|
return sorted(display_list)
|
|
return sorted(display_list)
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
# Use set for O(1) lookup instead of list
|
|
# Use set for O(1) lookup instead of list
|
|
|
mru_repos = sorted([f"⭐ {d}" for d in display_list if d in mru_set])
|
|
mru_repos = sorted([f"⭐ {d}" for d in display_list if d in mru_set])
|
|
|
non_mru_repos = sorted([d for d in display_list if d not in mru_set])
|
|
non_mru_repos = sorted([d for d in display_list if d not in mru_set])
|
|
@@ -131,10 +131,10 @@ def find_window_by_app_id(app_id):
|
|
|
def focus_or_launch_alacritty(class_name, title, working_dir, session_name):
|
|
def focus_or_launch_alacritty(class_name, title, working_dir, session_name):
|
|
|
debug(f"focus_or_launch_alacritty: class={class_name}, title={title}, dir={working_dir}")
|
|
debug(f"focus_or_launch_alacritty: class={class_name}, title={title}, dir={working_dir}")
|
|
|
window_id = find_window_by_app_id(class_name)
|
|
window_id = find_window_by_app_id(class_name)
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
if window_id:
|
|
if window_id:
|
|
|
debug("Found existing window, focusing...")
|
|
debug("Found existing window, focusing...")
|
|
|
- subprocess.run(["niri", "msg", "action", "focus-window", "--id", window_id],
|
|
|
|
|
|
|
+ subprocess.run(["niri", "msg", "action", "focus-window", "--id", window_id],
|
|
|
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
|
else:
|
|
else:
|
|
|
debug("No existing window found, launching alacritty...")
|
|
debug("No existing window found, launching alacritty...")
|
|
@@ -148,7 +148,7 @@ else
|
|
|
tmux attach -t '{session_name}';
|
|
tmux attach -t '{session_name}';
|
|
|
fi"""
|
|
fi"""
|
|
|
subprocess.Popen(
|
|
subprocess.Popen(
|
|
|
- ["alacritty", f"--class={class_name}", f"--title={title}",
|
|
|
|
|
|
|
+ ["alacritty", f"--class={class_name}", f"--title={title}",
|
|
|
f"--working-directory={working_dir}", "-e", "bash", "-c", tmux_cmd],
|
|
f"--working-directory={working_dir}", "-e", "bash", "-c", tmux_cmd],
|
|
|
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, start_new_session=True,
|
|
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, start_new_session=True,
|
|
|
)
|
|
)
|
|
@@ -160,13 +160,13 @@ def main():
|
|
|
parser.add_argument("--no-cache", action="store_true", help="Bypass cache and rescan repositories")
|
|
parser.add_argument("--no-cache", action="store_true", help="Bypass cache and rescan repositories")
|
|
|
parser.add_argument("--clear-cache", action="store_true", help="Clear cache and MRU files")
|
|
parser.add_argument("--clear-cache", action="store_true", help="Clear cache and MRU files")
|
|
|
args = parser.parse_args()
|
|
args = parser.parse_args()
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
debug(f"Starting niri-dev-launcher with args: {sys.argv[1:]}")
|
|
debug(f"Starting niri-dev-launcher with args: {sys.argv[1:]}")
|
|
|
debug(f"DEV_DIR: {args.dev_dir}")
|
|
debug(f"DEV_DIR: {args.dev_dir}")
|
|
|
cache_file, mru_file = get_cache_files(args.dev_dir)
|
|
cache_file, mru_file = get_cache_files(args.dev_dir)
|
|
|
debug(f"CACHE_FILE: {cache_file}")
|
|
debug(f"CACHE_FILE: {cache_file}")
|
|
|
debug(f"MRU_FILE: {mru_file}")
|
|
debug(f"MRU_FILE: {mru_file}")
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
if args.clear_cache:
|
|
if args.clear_cache:
|
|
|
if cache_file.exists():
|
|
if cache_file.exists():
|
|
|
cache_file.unlink()
|
|
cache_file.unlink()
|
|
@@ -174,13 +174,13 @@ def main():
|
|
|
mru_file.unlink()
|
|
mru_file.unlink()
|
|
|
subprocess.run(["notify-send", "Niri Dev Launcher", "Cache cleared"], check=False)
|
|
subprocess.run(["notify-send", "Niri Dev Launcher", "Cache cleared"], check=False)
|
|
|
sys.exit(0)
|
|
sys.exit(0)
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
repos = find_git_repos(args.dev_dir, args.no_cache)
|
|
repos = find_git_repos(args.dev_dir, args.no_cache)
|
|
|
if not repos:
|
|
if not repos:
|
|
|
debug("No repositories found!")
|
|
debug("No repositories found!")
|
|
|
subprocess.run(["notify-send", "Niri Dev Launcher", f"No git repositories found in {args.dev_dir}"], check=False)
|
|
subprocess.run(["notify-send", "Niri Dev Launcher", f"No git repositories found in {args.dev_dir}"], check=False)
|
|
|
sys.exit(1)
|
|
sys.exit(1)
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
debug("Found repositories, proceeding...")
|
|
debug("Found repositories, proceeding...")
|
|
|
display_to_path = {}
|
|
display_to_path = {}
|
|
|
display_list = []
|
|
display_list = []
|
|
@@ -188,13 +188,13 @@ def main():
|
|
|
display = str(Path(repo).relative_to(args.dev_dir)) if repo.startswith(args.dev_dir) else repo
|
|
display = str(Path(repo).relative_to(args.dev_dir)) if repo.startswith(args.dev_dir) else repo
|
|
|
display_list.append(display)
|
|
display_list.append(display)
|
|
|
display_to_path[display] = repo
|
|
display_to_path[display] = repo
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
debug(f"Mapped {len(display_to_path)} projects")
|
|
debug(f"Mapped {len(display_to_path)} projects")
|
|
|
debug("Sorting by MRU...")
|
|
debug("Sorting by MRU...")
|
|
|
sorted_list = sort_by_mru(display_list, args.dev_dir)
|
|
sorted_list = sort_by_mru(display_list, args.dev_dir)
|
|
|
debug("Presenting in fuzzel...")
|
|
debug("Presenting in fuzzel...")
|
|
|
-
|
|
|
|
|
- # Use default font to avoid subprocess call - can be overridden via env if needed
|
|
|
|
|
|
|
+
|
|
|
|
|
+ # Use default font to avoid subprocess call - can be overridden via env if needed
|
|
|
fuzzel_process = subprocess.Popen(
|
|
fuzzel_process = subprocess.Popen(
|
|
|
["fuzzel", "--prompt", "💀 Poison: ", "--dmenu", "--width", "30", "--lines", "20",
|
|
["fuzzel", "--prompt", "💀 Poison: ", "--dmenu", "--width", "30", "--lines", "20",
|
|
|
"--border-width", "2", "--background-color", "#191724ff",
|
|
"--border-width", "2", "--background-color", "#191724ff",
|
|
@@ -202,27 +202,27 @@ def main():
|
|
|
"--selection-text-color", "#31748fff", "--selection-match-color", "#31748fff", "--prompt-color", "#f6c177ff"],
|
|
"--selection-text-color", "#31748fff", "--selection-match-color", "#31748fff", "--prompt-color", "#f6c177ff"],
|
|
|
stdin=subprocess.PIPE, stdout=subprocess.PIPE, text=True,
|
|
stdin=subprocess.PIPE, stdout=subprocess.PIPE, text=True,
|
|
|
)
|
|
)
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
stdout, _ = fuzzel_process.communicate(input="\n".join(sorted_list))
|
|
stdout, _ = fuzzel_process.communicate(input="\n".join(sorted_list))
|
|
|
selected_display = stdout.strip()
|
|
selected_display = stdout.strip()
|
|
|
debug(f"Selected display: '{selected_display}'")
|
|
debug(f"Selected display: '{selected_display}'")
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
if not selected_display:
|
|
if not selected_display:
|
|
|
debug("No selection, exiting")
|
|
debug("No selection, exiting")
|
|
|
sys.exit(0)
|
|
sys.exit(0)
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
selected_display_clean = selected_display.replace("⭐ ", "", 1)
|
|
selected_display_clean = selected_display.replace("⭐ ", "", 1)
|
|
|
selected = display_to_path.get(selected_display_clean, "")
|
|
selected = display_to_path.get(selected_display_clean, "")
|
|
|
debug(f"Selected display (clean): '{selected_display_clean}'")
|
|
debug(f"Selected display (clean): '{selected_display_clean}'")
|
|
|
debug(f"Selected path: '{selected}'")
|
|
debug(f"Selected path: '{selected}'")
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
if not selected or not Path(selected).is_dir():
|
|
if not selected or not Path(selected).is_dir():
|
|
|
debug("Invalid selection, exiting")
|
|
debug("Invalid selection, exiting")
|
|
|
sys.exit(0)
|
|
sys.exit(0)
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
debug("Updating MRU...")
|
|
debug("Updating MRU...")
|
|
|
update_mru(selected_display_clean, args.dev_dir)
|
|
update_mru(selected_display_clean, args.dev_dir)
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
project_name = get_project_name(selected)
|
|
project_name = get_project_name(selected)
|
|
|
session_name = f"dev-{project_name}"
|
|
session_name = f"dev-{project_name}"
|
|
|
class_name = f"com.mzunino.dev.{project_name}"
|
|
class_name = f"com.mzunino.dev.{project_name}"
|
|
@@ -230,7 +230,7 @@ def main():
|
|
|
debug(f"Session name: {session_name}")
|
|
debug(f"Session name: {session_name}")
|
|
|
debug(f"Class name: {class_name}")
|
|
debug(f"Class name: {class_name}")
|
|
|
debug("Focusing or launching alacritty...")
|
|
debug("Focusing or launching alacritty...")
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
focus_or_launch_alacritty(class_name, project_name, selected, session_name)
|
|
focus_or_launch_alacritty(class_name, project_name, selected, session_name)
|
|
|
|
|
|
|
|
|
|
|