Source code for mercurial.utils.launcher

import os
import subprocess
import threading
import tkinter as tk
from tkinter import messagebox, scrolledtext, ttk

# === CONFIG ===
PROJECT_ROOT = r"F:\Documents\mercurial_project"
CONDA_PYTHON = r"C:\Users\Miguel\anaconda3\envs\mercurial\python.exe"

# === DARK THEME COLORS ===
BG_COLOR = "#1e1e1e"
FG_COLOR = "#d4d4d4"
ACCENT_COLOR = "#007acc"
LISTBOX_BG = "#252526"
OUTPUT_BG = "#1e1e1e"
BUTTON_BG = "#3c3c3c"
BUTTON_ACTIVE = "#505050"
ENTRY_BG = "#2d2d2d"
PLACEHOLDER_COLOR = "#888888"

# Force UTF-8 globally
os.environ["PYTHONUTF8"] = "1"
try:
    import sys

    sys.stdout.reconfigure(encoding="utf-8")
    sys.stderr.reconfigure(encoding="utf-8")
except Exception:
    pass


[docs] class ToolTip: def __init__(self, widget, text): self.widget = widget self.text = text self.tip_window = None widget.bind("<Enter>", self.show_tip) widget.bind("<Leave>", self.hide_tip)
[docs] def show_tip(self, _=None): if self.tip_window or not self.text: return x, y, _, _ = self.widget.bbox("insert") if self.widget.winfo_viewable() else (0, 0, 0, 0) x = x + self.widget.winfo_rootx() + 20 y = y + self.widget.winfo_rooty() + 20 self.tip_window = tw = tk.Toplevel(self.widget) tw.wm_overrideredirect(True) tw.wm_geometry(f"+{x}+{y}") label = tk.Label( tw, text=self.text, bg="#222", fg="white", relief="solid", borderwidth=1, font=("Segoe UI", 9), ) label.pack()
[docs] def hide_tip(self, _=None): if self.tip_window: self.tip_window.destroy() self.tip_window = None
[docs] class ScriptLauncher: def __init__(self, root): self.root = root self.root.title("Python Launcher IDE ๐Ÿ”ฅ") self.root.geometry("950x650") self.root.configure(bg=BG_COLOR) # Scripts storage self.scripts = [] self.filtered_scripts = [] self.script_to_category = {} # โœ… Track running process self.current_process = None # === SEARCH BAR === self.search_var = tk.StringVar() self.search_entry = tk.Entry( root, textvariable=self.search_var, bg=ENTRY_BG, fg=PLACEHOLDER_COLOR, # start as placeholder color insertbackground="white", relief=tk.FLAT, ) self.search_entry.pack(fill="x", padx=10, pady=5) # Set placeholder text self.placeholder_text = "Search scripts..." self.search_entry.insert(0, self.placeholder_text) # Bind focus events self.search_entry.bind("<FocusIn>", self._clear_placeholder) self.search_entry.bind("<FocusOut>", self._add_placeholder) # === MAIN FRAME === main_frame = tk.Frame(root, bg=BG_COLOR) main_frame.pack(fill="both", expand=True) # === CATEGORY TREE === self.tree = ttk.Treeview(main_frame) self.tree.pack(side="left", fill="y", padx=5, pady=5) self.tree.bind("<<TreeviewSelect>>", self.on_tree_select) style = ttk.Style() style.theme_use("default") style.configure( "Treeview", background=LISTBOX_BG, foreground=FG_COLOR, fieldbackground=LISTBOX_BG, highlightthickness=0, borderwidth=0, ) style.map( "Treeview", background=[("selected", ACCENT_COLOR)], foreground=[("selected", "white")] ) # === SCRIPT LIST === self.listbox = tk.Listbox( main_frame, bg=LISTBOX_BG, fg=FG_COLOR, selectbackground=ACCENT_COLOR, borderwidth=0 ) self.listbox.pack(side="left", fill="both", expand=True, padx=5, pady=5) # === BUTTONS === btn_frame = tk.Frame(root, bg="#151515", height=45) btn_frame.pack(fill="x") btn_frame.pack_propagate(False) run_btn = IDEButton( btn_frame, text="๐Ÿš€ Run", command=self.run_script, fg="white", bg="#1f7a1f", hover_bg="#2aa82a", activebackground="#145214", relief=tk.FLAT, ) run_btn.pack(side="left", padx=8, pady=8) ToolTip(run_btn, "Run selected script") refresh_btn = IDEButton( btn_frame, text="๐Ÿ”„ Refresh", command=self.load_scripts, fg="white", bg="#1f4e79", hover_bg="#2f6fa8", activebackground="#163a5c", relief=tk.FLAT, ) refresh_btn.pack(side="left", padx=8, pady=8) ToolTip(refresh_btn, "Reload all scripts from disk") clear_btn = IDEButton( btn_frame, text="๐Ÿงน Clear", command=self.clear_output, fg="white", bg="#8a5a00", hover_bg="#a86d00", activebackground="#6b4500", relief=tk.FLAT, ) clear_btn.pack(side="left", padx=8, pady=8) ToolTip(clear_btn, "Clear output console") kill_btn = IDEButton( btn_frame, text="๐Ÿ’€ Kill", command=self.kill_script, fg="white", bg="#8b0000", hover_bg="#a00000", activebackground="#5c0000", relief=tk.FLAT, ) kill_btn.pack(side="left", padx=8, pady=8) ToolTip(kill_btn, "Force stop running script") # === OUTPUT === self.output = scrolledtext.ScrolledText( root, height=15, bg=OUTPUT_BG, fg=FG_COLOR, insertbackground="white", borderwidth=0 ) self.output.pack(fill="both", expand=True, padx=10, pady=10) # === PROGRESS BAR === self.progress = ttk.Progressbar(root, mode="indeterminate") self.progress.pack(fill="x", padx=10, pady=(0, 10)) # Load scripts and bind search self.load_scripts() self.search_var.trace_add("write", self.filter_scripts)
[docs] def load_scripts(self): self.scripts.clear() self.script_to_category.clear() self.tree.delete(*self.tree.get_children()) root_node = self.tree.insert("", "end", text="All Scripts", open=True) for root_dir, _, files in os.walk(PROJECT_ROOT): for file in files: if file.endswith(".py") and file != "__init__.py": full_path = os.path.join(root_dir, file) self.scripts.append(full_path) parts = os.path.relpath(full_path, PROJECT_ROOT).split(os.sep)[:-1] parent = root_node for part in parts: children = self.tree.get_children(parent) found = next( (c for c in children if self.tree.item(c, "text") == part), None ) parent = found or self.tree.insert(parent, "end", text=part, open=True) script_id = self.tree.insert(parent, "end", text=file) self.script_to_category[script_id] = full_path self.show_scripts(self.scripts)
[docs] def on_tree_select(self, event): selected = self.tree.selection() if not selected: return script_paths = [] for node in selected: if node in self.script_to_category: script_paths.append(self.script_to_category[node]) else: script_paths.extend(self.get_all_scripts_in_node(node)) self.show_scripts(script_paths)
[docs] def get_all_scripts_in_node(self, node): scripts = [] for child in self.tree.get_children(node): if child in self.script_to_category: scripts.append(self.script_to_category[child]) else: scripts.extend(self.get_all_scripts_in_node(child)) return scripts
[docs] def show_scripts(self, script_list): self.filtered_scripts = script_list self.listbox.delete(0, tk.END) for path in script_list: self.listbox.insert(tk.END, os.path.relpath(path, PROJECT_ROOT))
[docs] def filter_scripts(self, *args): query = self.search_var.get().lower().strip() if not query or query == self.placeholder_text.lower(): self.show_scripts(self.scripts) return filtered = [s for s in self.scripts if query in os.path.relpath(s, PROJECT_ROOT).lower()] self.show_scripts(filtered)
[docs] def run_script(self): selection = self.listbox.curselection() if not selection: messagebox.showwarning("No selection", "Pick a script first ๐Ÿ˜„") return script_path = self.filtered_scripts[selection[0]] self.output.delete(1.0, tk.END) self.progress.start(50) threading.Thread(target=self.execute_script, args=(script_path,), daemon=True).start()
[docs] def execute_script(self, script_path): env = os.environ.copy() env["PYTHONUTF8"] = "1" env["PYTHONPATH"] = PROJECT_ROOT self.current_process = subprocess.Popen( [CONDA_PYTHON, script_path], cwd=PROJECT_ROOT, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, encoding="utf-8", errors="replace", ) for line in self.current_process.stdout: self._update_output(line) self.current_process.wait() self._update_output(f"\n[Done] Exit code: {self.current_process.returncode}\n") self.progress.stop() self.current_process = None
[docs] def clear_output(self): self.output.delete(1.0, tk.END) self.progress.stop()
# โœ… KILLSWITCH LOGIC
[docs] def kill_script(self): if self.current_process and self.current_process.poll() is None: try: self.current_process.terminate() self._update_output("\n[!] Script terminated by user\n") except Exception as e: self._update_output(f"\n[Error killing process]: {e}\n") finally: self.progress.stop() else: messagebox.showinfo("No running script", "Nothing is currently running ๐Ÿ˜„")
def _update_output(self, text): def inner(): self.output.insert(tk.END, text) self.output.see(tk.END) self.output.after(0, inner) def _clear_placeholder(self, event): if self.search_entry.get() == self.placeholder_text: self.search_entry.delete(0, tk.END) self.search_entry.config(fg=FG_COLOR) def _add_placeholder(self, event): if not self.search_entry.get(): self.search_entry.insert(0, self.placeholder_text) self.search_entry.config(fg=PLACEHOLDER_COLOR)
[docs] class IDEButton(tk.Button): def __init__(self, master, **kwargs): self.base_bg = kwargs.pop("bg", "#333") self.hover_bg = kwargs.pop("hover_bg", "#444") self.active_bg = kwargs.pop("activebackground", "#555") super().__init__(master, bg=self.base_bg, activebackground=self.active_bg, **kwargs) self.default_y = 0 self.bind("<Enter>", self.on_enter) self.bind("<Leave>", self.on_leave)
[docs] def on_enter(self, _): self.config(bg=self.hover_bg) self.place_configure(y=self.default_y - 1)
[docs] def on_leave(self, _): self.config(bg=self.base_bg) self.place_configure(y=self.default_y)
if __name__ == "__main__": if not os.path.exists(PROJECT_ROOT): print("Invalid PROJECT_ROOT path!") exit() if not os.path.exists(CONDA_PYTHON): print("Invalid CONDA_PYTHON path!") exit() root = tk.Tk() app = ScriptLauncher(root) root.mainloop()