🧬 Vertex Editor · 开源公开版

⚡ Vertex 编辑器
Build EXE · 多语言 IDE

完整 Python 源代码 · 支持 C/C++/C#/Rust 一键构建 | 暗色主题 | 现代化工具链
🐍 Python 3.10+
import customtkinter as ctk
from tkinter import filedialog, messagebox
from tkinter import font as tkfont
from tkinter import ttk
import tkinter as tk
import os
import re
import json
from pathlib import Path
from datetime import datetime
import threading
import subprocess
import sys
import queue
import shutil
import platform

ctk.set_appearance_mode("dark")
ctk.set_default_color_theme("dark-blue")

GRADIENT_COLORS = {
    "bg_primary": "#0D1117",
    "bg_secondary": "#161B22",
    "bg_tertiary": "#21262D",
    "border": "#30363D",
    "text_primary": "#E6EDF3",
    "text_secondary": "#8B949E",
    "text_tertiary": "#6E7681",
    "accent_blue": "#58A6FF",
    "accent_green": "#3FB950",
    "accent_orange": "#D29922",
    "accent_red": "#F85149",
    "accent_purple": "#A371F7",
    "accent_pink": "#DB61A2",
    "accent_cyan": "#39D2C0",
    "accent_yellow": "#E3B341",
    "syntax_keyword": "#FF7B72",
    "syntax_string": "#A5D6FF",
    "syntax_comment": "#8B949E",
    "syntax_number": "#79C0FF",
    "syntax_type": "#FFA657",
    "syntax_function": "#D2A8FF",
    "syntax_variable": "#FFA198",
    "syntax_operator": "#FF7B72",
    "syntax_preprocessor": "#8B949E",
    "syntax_tag": "#7EE787",
    "syntax_attr": "#79C0FF",
    "syntax_directive": "#D2A8FF",
    "syntax_expression": "#A5D6FF",
    "gutter_bg": "#0D1117",
    "gutter_fg": "#484F58",
    "gutter_active": "#6E7681",
    "line_highlight": "#1C2128",
    "selection": "#264F78",
    "cursor": "#58A6FF",
    "scrollbar": "#30363D",
    "scrollbar_hover": "#484F58",
    "toolbar_bg": "#161B22",
    "statusbar_bg": "#161B22",
    "tab_active_bg": "#1C2128",
    "tab_inactive_bg": "#0D1117",
    "dropdown_bg": "#21262D",
    "dropdown_hover": "#30363D",
    "menu_bg": "#161B22",
    "menu_hover": "#1C2128",
    "dialog_bg": "#161B22",
    "button_primary": "#238636",
    "button_primary_hover": "#2EA043",
    "button_secondary": "#21262D",
    "button_secondary_hover": "#30363D",
    "button_danger": "#DA3633",
    "button_danger_hover": "#F85149",
    "input_bg": "#0D1117",
    "input_border": "#30363D",
    "input_focus": "#58A6FF",
    "terminal_bg": "#0D1117",
    "terminal_fg": "#E6EDF3",
    "terminal_prompt": "#3FB950",
    "terminal_error": "#F85149",
    "terminal_warning": "#D29922",
    "notification_bg": "#21262D",
    "notification_info": "#58A6FF",
    "notification_success": "#3FB950",
    "notification_warning": "#D29922",
    "notification_error": "#F85149",
}

class BuildSystem:
    def __init__(self, editor):
        self.editor = editor
        self.output_callback = None
        
    def check_tool(self, tool_name):
        return shutil.which(tool_name) is not None
    
    def build_python_exe(self, file_path, output_callback=None):
        self.output_callback = output_callback
        
        if not self.check_tool("nuitka") and not self.check_tool("nuitka3"):
            if self.check_tool("pip") or self.check_tool("pip3"):
                pip = "pip3" if self.check_tool("pip3") else "pip"
                self._output("Installing Nuitka...")
                subprocess.run([pip, "install", "nuitka"], capture_output=True)
            else:
                return False, "pip not found. Install Python first."
        
        if not self.check_tool("nuitka") and not self.check_tool("nuitka3"):
            return False, "Nuitka not found. Install: pip install nuitka"
        
        nuitka = "nuitka3" if self.check_tool("nuitka3") else "nuitka"
        
        output_dir = os.path.join(os.path.dirname(file_path), "build_output")
        os.makedirs(output_dir, exist_ok=True)
        
        cmd = [
            nuitka,
            "--standalone",
            "--onefile",
            "--output-dir=" + output_dir,
            file_path
        ]
        
        if platform.system() == "Windows":
            cmd.append("--windows-icon-from-ico=NONE")
            cmd.append("--mingw64")
        
        self._output(f"🔨 Building Python exe with Nuitka...")
        self._output(f"Command: {' '.join(cmd)}")
        
        try:
            process = subprocess.Popen(
                cmd,
                stdout=subprocess.PIPE,
                stderr=subprocess.STDOUT,
                text=True,
                bufsize=1
            )
            
            for line in process.stdout:
                self._output(line.strip())
            
            process.wait()
            
            if process.returncode == 0:
                exe_name = os.path.splitext(os.path.basename(file_path))[0]
                if platform.system() == "Windows":
                    exe_name += ".exe"
                exe_path = os.path.join(output_dir, exe_name)
                
                if os.path.exists(exe_path):
                    return True, f"Build successful!\nOutput: {exe_path}"
                else:
                    return True, f"Build completed. Check: {output_dir}"
            else:
                return False, f"Build failed with code {process.returncode}"
                
        except Exception as e:
            return False, str(e)
    
    def build_cpp_exe(self, file_path, output_callback=None):
        self.output_callback = output_callback
        
        if platform.system() == "Windows":
            compiler = "g++" if self.check_tool("g++") else "cl"
            if compiler == "cl":
                return self._build_cpp_msvc(file_path)
        else:
            compiler = "g++" if self.check_tool("g++") else "clang++"
        
        if not self.check_tool(compiler):
            if platform.system() == "Linux":
                return False, "Install g++: sudo apt install g++"
            elif platform.system() == "Darwin":
                return False, "Install Xcode Command Line Tools: xcode-select --install"
            else:
                return False, f"Install {compiler}"
        
        output_dir = os.path.join(os.path.dirname(file_path), "build_output")
        os.makedirs(output_dir, exist_ok=True)
        
        exe_name = os.path.splitext(os.path.basename(file_path))[0]
        if platform.system() == "Windows":
            exe_name += ".exe"
        
        output_path = os.path.join(output_dir, exe_name)
        
        cmd = [compiler, "-O3", "-std=c++17", file_path, "-o", output_path]
        
        if platform.system() == "Windows":
            cmd.extend(["-static", "-static-libgcc", "-static-libstdc++"])
        
        self._output(f"🔨 Building C/C++ exe...")
        self._output(f"Command: {' '.join(cmd)}")
        
        try:
            result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
            
            if result.returncode == 0:
                if os.path.exists(output_path):
                    return True, f"Build successful!\nOutput: {output_path}"
                else:
                    return True, f"Build completed. Check: {output_dir}"
            else:
                return False, f"Build failed:\n{result.stderr}"
        except subprocess.TimeoutExpired:
            return False, "Build timed out"
        except Exception as e:
            return False, str(e)
    
    def _build_cpp_msvc(self, file_path):
        self._output("🔨 Building with MSVC...")
        
        output_dir = os.path.join(os.path.dirname(file_path), "build_output")
        os.makedirs(output_dir, exist_ok=True)
        
        exe_name = os.path.splitext(os.path.basename(file_path))[0] + ".exe"
        output_path = os.path.join(output_dir, exe_name)
        
        cmd = ["cl", "/O2", "/EHsc", f"/Fe:{output_path}", file_path]
        
        try:
            vcvars = self._find_vcvars()
            if vcvars:
                full_cmd = f'call "{vcvars}" && {" ".join(cmd)}'
                result = subprocess.run(full_cmd, shell=True, capture_output=True, text=True, timeout=120)
            else:
                result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
            
            if result.returncode == 0:
                return True, f"Build successful!\nOutput: {output_path}"
            else:
                return False, f"Build failed:\n{result.stderr}"
        except Exception as e:
            return False, str(e)
    
    def _find_vcvars(self):
        paths = [
            "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\VC\\Auxiliary\\Build\\vcvars64.bat",
            "C:\\Program Files\\Microsoft Visual Studio\\2022\\Professional\\VC\\Auxiliary\\Build\\vcvars64.bat",
            "C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\VC\\Auxiliary\\Build\\vcvars64.bat",
            "C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\VC\\Auxiliary\\Build\\vcvars64.bat",
            "C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Professional\\VC\\Auxiliary\\Build\\vcvars64.bat",
            "C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Enterprise\\VC\\Auxiliary\\Build\\vcvars64.bat",
        ]
        for path in paths:
            if os.path.exists(path):
                return path
        return None
    
    def build_csharp_exe(self, file_path, output_callback=None):
        self.output_callback = output_callback
        
        if not self.check_tool("dotnet"):
            return False, "Install .NET SDK: https://dotnet.microsoft.com/download"
        
        if not self.check_tool("csc"):
            csc = self._find_csc()
            if not csc:
                return False, "csc not found. Install .NET Framework SDK or use dotnet"
        else:
            csc = "csc"
        
        output_dir = os.path.join(os.path.dirname(file_path), "build_output")
        os.makedirs(output_dir, exist_ok=True)
        
        exe_name = os.path.splitext(os.path.basename(file_path))[0] + ".exe"
        output_path = os.path.join(output_dir, exe_name)
        
        cmd = [csc, "/out:" + output_path, "/optimize+", file_path]
        
        self._output(f"🔨 Building C# exe...")
        
        try:
            result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
            if result.returncode == 0:
                return True, f"Build successful!\nOutput: {output_path}"
            else:
                return False, f"Build failed:\n{result.stderr}"
        except Exception as e:
            return False, str(e)
    
    def _find_csc(self):
        paths = [
            "C:\\Windows\\Microsoft.NET\\Framework64\\v4.0.30319\\csc.exe",
            "C:\\Windows\\Microsoft.NET\\Framework\\v4.0.30319\\csc.exe",
        ]
        for path in paths:
            if os.path.exists(path):
                return path
        return None
    
    def build_rust_exe(self, file_path, output_callback=None):
        self.output_callback = output_callback
        
        if not self.check_tool("rustc"):
            return False, "Install Rust: https://rustup.rs"
        
        output_dir = os.path.join(os.path.dirname(file_path), "build_output")
        os.makedirs(output_dir, exist_ok=True)
        
        exe_name = os.path.splitext(os.path.basename(file_path))[0]
        if platform.system() == "Windows":
            exe_name += ".exe"
        
        output_path = os.path.join(output_dir, exe_name)
        
        cmd = ["rustc", "-C", "opt-level=3", file_path, "-o", output_path]
        
        self._output(f"🔨 Building Rust exe...")
        
        try:
            result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
            if result.returncode == 0:
                return True, f"Build successful!\nOutput: {output_path}"
            else:
                return False, f"Build failed:\n{result.stderr}"
        except Exception as e:
            return False, str(e)
    
    def build_exe(self, file_path, language, output_callback=None):
        if not file_path:
            return False, "No file to build"
        
        if not os.path.exists(file_path):
            return False, "File not found"
        
        builders = {
            "python": self.build_python_exe,
            "cpp": self.build_cpp_exe,
            "c": self.build_cpp_exe,
            "csharp": self.build_csharp_exe,
            "cs": self.build_csharp_exe,
            "rust": self.build_rust_exe,
        }
        
        if language in builders:
            return builders[language](file_path, output_callback)
        else:
            return False, f"No exe builder for {language}"
    
    def _output(self, text):
        if self.output_callback:
            self.output_callback(text)

class BuildDialog(ctk.CTkToplevel):
    def __init__(self, master, editor):
        super().__init__(master)
        self.editor = editor
        self.build_system = BuildSystem(editor)
        
        self.title("📦 Build EXE")
        self.geometry("700x550")
        self.configure(fg_color=GRADIENT_COLORS["dialog_bg"])
        self.resizable(True, True)
        
        self.setup_ui()
        self.auto_detect()
        
    def setup_ui(self):
        colors = GRADIENT_COLORS
        
        title = ctk.CTkLabel(
            self,
            text="📦 Build EXE - One Click Compile",
            font=("Segoe UI", 20, "bold"),
            text_color=colors["accent_blue"]
        )
        title.pack(pady=15)
        
        info_frame = ctk.CTkFrame(self, fg_color=colors["bg_secondary"], corner_radius=10)
        info_frame.pack(fill="x", padx=20, pady=10)
        
        info_grid = ctk.CTkFrame(info_frame, fg_color="transparent")
        info_grid.pack(padx=15, pady=10, fill="x")
        
        ctk.CTkLabel(info_grid, text="File:", font=("Segoe UI", 11), text_color=colors["text_secondary"]).grid(row=0, column=0, sticky="w", pady=3)
        self.file_label = ctk.CTkLabel(info_grid, text="None", font=("Segoe UI", 11, "bold"), text_color=colors["text_primary"])
        self.file_label.grid(row=0, column=1, sticky="w", padx=10, pady=3)
        
        ctk.CTkLabel(info_grid, text="Language:", font=("Segoe UI", 11), text_color=colors["text_secondary"]).grid(row=1, column=0, sticky="w", pady=3)
        self.lang_label = ctk.CTkLabel(info_grid, text="Unknown", font=("Segoe UI", 11, "bold"), text_color=colors["accent_green"])
        self.lang_label.grid(row=1, column=1, sticky="w", padx=10, pady=3)
        
        ctk.CTkLabel(info_grid, text="Output:", font=("Segoe UI", 11), text_color=colors["text_secondary"]).grid(row=2, column=0, sticky="w", pady=3)
        self.output_label = ctk.CTkLabel(info_grid, text="build_output/", font=("Segoe UI", 11, "bold"), text_color=colors["accent_yellow"])
        self.output_label.grid(row=2, column=1, sticky="w", padx=10, pady=3)
        
        methods_frame = ctk.CTkFrame(self, fg_color=colors["bg_secondary"], corner_radius=10)
        methods_frame.pack(fill="x", padx=20, pady=10)
        
        ctk.CTkLabel(
            methods_frame,
            text="Build Method",
            font=("Segoe UI", 13, "bold"),
            text_color=colors["text_primary"]
        ).pack(pady=10)
        
        self.method_var = tk.StringVar(value="auto")
        
        methods_grid = ctk.CTkFrame(methods_frame, fg_color="transparent")
        methods_grid.pack(padx=20, pady=10, fill="x")
        
        methods = [
            ("auto", "🤖 Auto Detect", "Automatically choose the best method"),
            ("nuitka", "🐍 Nuitka (Python)", "Compile Python to standalone exe"),
            ("gcc", "⚡ GCC/G++ (C/C++)", "Compile C/C++ with optimizations"),
            ("msvc", "🪟 MSVC (C/C++/C#)", "Microsoft Visual C++ Compiler"),
            ("rustc", "🦀 Rustc (Rust)", "Rust compiler with release optimizations"),
        ]
        
        for i, (value, title, desc) in enumerate(methods):
            rb = ctk.CTkRadioButton(
                methods_grid,
                text="",
                variable=self.method_var,
                value=value,
                fg_color=colors["accent_blue"],
                command=self.on_method_change
            )
            rb.grid(row=i, column=0, pady=5)
            
            ctk.CTkLabel(
                methods_grid,
                text=title,
                font=("Segoe UI", 12, "bold"),
                text_color=colors["text_primary"]
            ).grid(row=i, column=1, sticky="w", padx=10)
            
            ctk.CTkLabel(
                methods_grid,
                text=desc,
                font=("Segoe UI", 10),
                text_color=colors["text_secondary"]
            ).grid(row=i, column=2, sticky="w", padx=10)
        
        self.output_text = ctk.CTkTextbox(
            self,
            font=("JetBrains Mono", 10),
            fg_color=colors["terminal_bg"],
            text_color=colors["terminal_fg"],
            height=150,
            corner_radius=8
        )
        self.output_text.pack(fill="both", expand=True, padx=20, pady=10)
        
        button_frame = ctk.CTkFrame(self, fg_color="transparent")
        button_frame.pack(fill="x", padx=20, pady=15)
        
        self.build_btn = ctk.CTkButton(
            button_frame,
            text="🔨 BUILD EXE",
            width=150,
            height=40,
            font=("Segoe UI", 13, "bold"),
            fg_color=colors["accent_green"],
            hover_color=colors["button_primary_hover"],
            command=self.start_build
        )
        self.build_btn.pack(side="left", padx=5)
        
        open_folder_btn = ctk.CTkButton(
            button_frame,
            text="📂 Open Output",
            width=120,
            height=40,
            font=("Segoe UI", 11),
            fg_color=colors["button_secondary"],
            hover_color=colors["button_secondary_hover"],
            command=self.open_output_folder
        )
        open_folder_btn.pack(side="left", padx=5)
        
        close_btn = ctk.CTkButton(
            button_frame,
            text="Close",
            width=100,
            height=40,
            font=("Segoe UI", 11),
            fg_color=colors["button_danger"],
            hover_color=colors["button_danger_hover"],
            command=self.destroy
        )
        close_btn.pack(side="right", padx=5)
    
    def auto_detect(self):
        if self.editor.current_file:
            self.file_label.configure(text=os.path.basename(self.editor.current_file))
            self.lang_label.configure(text=self.editor.lang_config.get("name", "Unknown"))
        else:
            self.file_label.configure(text="⚠️ No file open")
            self.lang_label.configure(text="⚠️ Save file first")
    
    def on_method_change(self):
        method = self.method_var.get()
        method_names = {
            "auto": "Auto",
            "nuitka": "Nuitka",
            "gcc": "GCC",
            "msvc": "MSVC",
            "rustc": "Rustc"
        }
        self.output_text.insert("end", f"🔧 Method changed to: {method_names.get(method, method)}\n")
        self.output_text.see("end")
    
    def start_build(self):
        if not self.editor.current_file:
            self.output_text.insert("end", "❌ Error: No file open. Save your file first!\n")
            return
        
        if self.editor.modified:
            self.output_text.insert("end", "💾 Auto-saving file...\n")
            self.editor.save_file()
        
        self.build_btn.configure(state="disabled", text="⏳ Building...")
        self.output_text.delete("1.0", "end")
        self.output_text.insert("end", "=" * 50 + "\n")
        self.output_text.insert("end", "🔨 VERTEX BUILD SYSTEM\n")
        self.output_text.insert("end", "=" * 50 + "\n\n")
        self.update()
        
        thread = threading.Thread(target=self.run_build)
        thread.daemon = True
        thread.start()
    
    def run_build(self):
        def output_callback(text):
            self.after(0, lambda: self.output_text.insert("end", text + "\n"))
            self.after(0, lambda: self.output_text.see("end"))
        
        success, message = self.build_system.build_exe(
            self.editor.current_file,
            self.editor.current_language,
            output_callback
        )
        
        self.after(0, lambda: self.output_text.insert("end", "\n" + "=" * 50 + "\n"))
        
        if success:
            self.after(0, lambda: self.output_text.insert("end", "✅ BUILD SUCCESSFUL!\n\n"))
            self.after(0, lambda: self.output_text.insert("end", message + "\n"))
            self.after(0, lambda: self.show_notification("Build Complete!", "success"))
        else:
            self.after(0, lambda: self.output_text.insert("end", "❌ BUILD FAILED!\n\n"))
            self.after(0, lambda: self.output_text.insert("end", message + "\n"))
            self.after(0, lambda: self.show_notification("Build Failed", "error"))
        
        self.after(0, lambda: self.build_btn.configure(state="normal", text="🔨 BUILD EXE"))
    
    def open_output_folder(self):
        if self.editor.current_file:
            output_dir = os.path.join(os.path.dirname(self.editor.current_file), "build_output")
            if os.path.exists(output_dir):
                if platform.system() == "Windows":
                    os.startfile(output_dir)
                elif platform.system() == "Darwin":
                    subprocess.run(["open", output_dir])
                else:
                    subprocess.run(["xdg-open", output_dir])
            else:
                self.output_text.insert("end", "⚠️ Output folder not found. Build first.\n")
    
    def show_notification(self, message, type_="info"):
        colors = {
            "info": GRADIENT_COLORS["notification_info"],
            "success": GRADIENT_COLORS["notification_success"],
            "error": GRADIENT_COLORS["notification_error"]
        }
        
        notification = ctk.CTkLabel(
            self,
            text=message,
            font=("Segoe UI", 12, "bold"),
            text_color=colors.get(type_, GRADIENT_COLORS["text_primary"]),
            fg_color=GRADIENT_COLORS["notification_bg"],
            corner_radius=8
        )
        notification.pack(pady=5)
        self.after(3000, notification.destroy)

class VertexEditor(ctk.CTk):
    def __init__(self):
        super().__init__()
        
        self.title("Vertex Editor - Build EXE Edition")
        self.geometry("1400x900")
        self.minsize(800, 600)
        
        self.current_file = None
        self.modified = False
        self.clipboard = ""
        self.font_size = 13
        self.tab_size = 4
        self.encoding = "utf-8"
        self.current_language = "text"
        self.lang_config = self.get_language_config("text")
        self.tools_panel_visible = True
        self.build_dialog = None
        
        self.init_style()
        self.setup_ui()
        self.setup_keybindings()
        self.new_file()
        
        self.protocol("WM_DELETE_WINDOW", self.on_close)
        self.after(100, self.update_ui)
        
    def init_style(self):
        style = ttk.Style()
        style.theme_use("clam")
        
        colors = GRADIENT_COLORS
        
        style.configure("Vertex.TFrame", background=colors["bg_primary"])
        style.configure("Vertex.TLabel", background=colors["bg_primary"], foreground=colors["text_primary"])
        style.configure("Vertex.TButton", 
                       background=colors["button_secondary"],
                       foreground=colors["text_primary"],
                       borderwidth=1,
                       bordercolor=colors["border"],
                       padding=6,
                       relief="flat")
        style.map("Vertex.TButton",
                 background=[("active", colors["button_secondary_hover"]),
                           ("pressed", colors["button_primary"])])
        
        style.configure("Vertex.Vertical.TScrollbar",
                       background=colors["scrollbar"],
                       bordercolor=colors["scrollbar"],
                       arrowcolor=colors["text_secondary"],
                       troughcolor=colors["bg_primary"])
        style.map("Vertex.Vertical.TScrollbar",
                 background=[("active", colors["scrollbar_hover"])])
                 
        style.configure("Vertex.TNotebook",
                       background=colors["bg_primary"],
                       bordercolor=colors["border"])
        style.configure("Vertex.TNotebook.Tab",
                       background=colors["tab_inactive_bg"],
                       foreground=colors["text_secondary"],
                       padding=[15, 5])
        style.map("Vertex.TNotebook.Tab",
                 background=[("selected", colors["tab_active_bg"])],
                 foreground=[("selected", colors["text_primary"])])
                 
        style.configure("Vertex.Treeview",
                       background=colors["bg_secondary"],
                       foreground=colors["text_primary"],
                       fieldbackground=colors["bg_secondary"])
        style.map("Vertex.Treeview",
                 background=[("selected", colors["selection"])])
                 
        style.configure("Vertex.TPanedwindow",
                       background=colors["bg_primary"],
                       bordercolor=colors["border"])
        
        self.option_add("*Font", ("Segoe UI", 10))
        self.option_add("*Background", colors["bg_primary"])
        self.option_add("*Foreground", colors["text_primary"])
        self.option_add("*selectBackground", colors["selection"])
        self.option_add("*selectForeground", colors["text_primary"])
        
    def setup_ui(self):
        colors = GRADIENT_COLORS
        
        self.main_container = ctk.CTkFrame(self, fg_color=colors["bg_primary"])
        self.main_container.pack(fill="both", expand=True)
        
        self.setup_toolbar()
        self.setup_main_area()
        self.setup_statusbar()
        self.setup_tools_panel()
        
    def setup_toolbar(self):
        colors = GRADIENT_COLORS
        
        self.toolbar = ctk.CTkFrame(self.main_container, height=40, fg_color=colors["toolbar_bg"], corner_radius=0)
        self.toolbar.pack(fill="x", side="top")
        
        self.toolbar_inner = ctk.CTkFrame(self.toolbar, fg_color="transparent")
        self.toolbar_inner.pack(fill="x", padx=5, pady=3)
        
        buttons = [
            ("📁 New", self.new_file),
            ("📂 Open", self.open_file_dialog),
            ("💾 Save", self.save_file),
            ("↩ Undo", self.undo),
            ("↪ Redo", self.redo),
            ("📋 Copy", self.copy),
            ("📌 Paste", self.paste),
            ("🔍 Find", self.toggle_search),
            ("📦 BUILD EXE", self.open_build_dialog),
            ("🛠️ Tools", self.toggle_tools_panel),
        ]
        
        for text, command in buttons:
            btn = ctk.CTkButton(
                self.toolbar_inner,
                text=text,
                width=70 if len(text) > 3 else 32,
                height=28,
                fg_color="transparent" if "BUILD" not in text else colors["accent_green"],
                hover_color=colors["button_secondary_hover"] if "BUILD" not in text else colors["button_primary_hover"],
                text_color=colors["text_primary"],
                font=("Segoe UI", 10, "bold") if "BUILD" in text else ("Segoe UI", 10),
                command=command
            )
            btn.pack(side="left", padx=2)
            
    def setup_main_area(self):
        colors = GRADIENT_COLORS
        
        self.main_paned = ttk.PanedWindow(self.main_container, orient="horizontal", style="Vertex.TPanedwindow")
        self.main_paned.pack(fill="both", expand=True, side="top")
        
        self.editor_container = ctk.CTkFrame(self.main_paned, fg_color=colors["bg_primary"])
        self.main_paned.add(self.editor_container, weight=4)
        
        self.editor_frame = ctk.CTkFrame(self.editor_container, fg_color=colors["bg_primary"])
        self.editor_frame.pack(fill="both", expand=True)
        
        self.line_numbers = ctk.CTkTextbox(
            self.editor_frame,
            width=50,
            font=("JetBrains Mono", self.font_size),
            fg_color=colors["gutter_bg"],
            text_color=colors["gutter_fg"],
            border_width=0,
            corner_radius=0,
            state="disabled"
        )
        self.line_numbers.pack(side="left", fill="y")
        
        self.editor_scrollbar = ctk.CTkScrollbar(
            self.editor_frame,
            orientation="vertical",
            fg_color=colors["scrollbar"],
            button_color=colors["scrollbar"],
            button_hover_color=colors["scrollbar_hover"]
        )
        self.editor_scrollbar.pack(side="right", fill="y")
        
        self.text_editor = ctk.CTkTextbox(
            self.editor_frame,
            font=("JetBrains Mono", self.font_size),
            fg_color=colors["bg_primary"],
            text_color=colors["text_primary"],
            border_width=0,
            corner_radius=0,
            wrap="none",
            undo=True,
            tabs=(self.tab_size * 10,)
        )
        self.text_editor.pack(side="left", fill="both", expand=True)
        
        self.text_editor.bind("", self.on_key_press)
        self.text_editor.bind("<>", self.on_text_modified)
        
    def setup_tools_panel(self):
        colors = GRADIENT_COLORS
        
        self.tools_container = ctk.CTkFrame(self.main_paned, fg_color=colors["bg_secondary"], width=350)
        self.main_paned.add(self.tools_container, weight=1)
        
        self.tools_header = ctk.CTkFrame(self.tools_container, fg_color=colors["bg_tertiary"], height=35)
        self.tools_header.pack(fill="x")
        self.tools_header.pack_propagate(False)
        
        self.tools_label = ctk.CTkLabel(
            self.tools_header,
            text="🛠️ TOOLBOX",
            font=("Segoe UI", 11, "bold"),
            text_color=colors["accent_blue"]
        )
        self.tools_label.pack(side="left", padx=10, pady=5)
        
        self.tools_notebook = ttk.Notebook(self.tools_container, style="Vertex.TNotebook")
        self.tools_notebook.pack(fill="both", expand=True)
        
        self.setup_terminal_tab()
        self.setup_file_explorer_tab()
        self.setup_build_quick_tab()
        
    def setup_terminal_tab(self):
        colors = GRADIENT_COLORS
        
        self.terminal_tab = ctk.CTkFrame(self.tools_notebook, fg_color=colors["terminal_bg"])
        self.tools_notebook.add(self.terminal_tab, text="  💻 Terminal  ")
        
        self.terminal_output = ctk.CTkTextbox(
            self.terminal_tab,
            font=("JetBrains Mono", 11),
            fg_color=colors["terminal_bg"],
            text_color=colors["terminal_fg"],
            border_width=0,
            corner_radius=0,
            wrap="word"
        )
        self.terminal_output.pack(fill="both", expand=True, padx=5, pady=5)
        
        self.terminal_input_frame = ctk.CTkFrame(self.terminal_tab, fg_color=colors["bg_tertiary"], height=30)
        self.terminal_input_frame.pack(fill="x", side="bottom")
        
        ctk.CTkLabel(
            self.terminal_input_frame,
            text="$",
            font=("JetBrains Mono", 11, "bold"),
            text_color=colors["terminal_prompt"]
        ).pack(side="left", padx=(5, 0))
        
        self.terminal_input = ctk.CTkEntry(
            self.terminal_input_frame,
            font=("JetBrains Mono", 11),
            fg_color=colors["terminal_bg"],
            text_color=colors["terminal_fg"],
            border_width=0,
            corner_radius=0
        )
        self.terminal_input.pack(side="left", fill="x", expand=True, padx=5, pady=3)
        self.terminal_input.bind("", self.execute_terminal_command)
        
        self.terminal_output.insert("end", "Vertex Terminal v1.0\n")
        self.terminal_output.insert("end", f"{'─' * 40}\n")
        self.terminal_output.insert("end", "$ ")
        
    def execute_terminal_command(self, event=None):
        command = self.terminal_input.get().strip()
        if not command:
            return
            
        self.terminal_input.delete(0, "end")
        self.terminal_output.insert("end", f"{command}\n")
        
        if command in ["clear", "cls"]:
            self.terminal_output.delete("1.0", "end")
            self.terminal_output.insert("end", "$ ")
            return
        
        try:
            if platform.system() == "Windows":
                process = subprocess.Popen(["cmd", "/c", command], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, shell=True)
            else:
                process = subprocess.Popen(["/bin/sh", "-c", command], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
                
            stdout, stderr = process.communicate(timeout=30)
            if stdout:
                self.terminal_output.insert("end", stdout)
            if stderr:
                self.terminal_output.insert("end", stderr)
        except subprocess.TimeoutExpired:
            process.kill()
            self.terminal_output.insert("end", "[Timeout]\n")
        except Exception as e:
            self.terminal_output.insert("end", f"[Error] {e}\n")
            
        self.terminal_output.insert("end", "$ ")
        self.terminal_output.see("end")
        
    def setup_file_explorer_tab(self):
        colors = GRADIENT_COLORS
        
        self.explorer_tab = ctk.CTkFrame(self.tools_notebook, fg_color=colors["bg_secondary"])
        self.tools_notebook.add(self.explorer_tab, text="  📁 Files  ")
        
        self.explorer_path_var = ctk.StringVar(value=os.path.expanduser("~"))
        self.explorer_path = ctk.CTkEntry(
            self.explorer_tab,
            textvariable=self.explorer_path_var,
            font=("Segoe UI", 10),
            fg_color=colors["input_bg"],
            text_color=colors["text_primary"],
            border_width=0
        )
        self.explorer_path.pack(fill="x", padx=5, pady=5)
        self.explorer_path.bind("", self.refresh_file_explorer)
        
        self.explorer_tree = ttk.Treeview(self.explorer_tab, style="Vertex.Treeview", show="tree")
        self.explorer_tree.pack(fill="both", expand=True)
        self.explorer_tree.bind("", self.on_explorer_double_click)
        
        self.refresh_file_explorer()
        
    def refresh_file_explorer(self, event=None):
        self.explorer_tree.delete(*self.explorer_tree.get_children())
        path = self.explorer_path_var.get()
        
        try:
            items = os.listdir(path)
            dirs = sorted([d for d in items if os.path.isdir(os.path.join(path, d))])
            files = sorted([f for f in items if os.path.isfile(os.path.join(path, f))])
            
            for d in dirs:
                self.explorer_tree.insert("", "end", text=f"📁 {d}", values=(os.path.join(path, d),))
            for f in files:
                self.explorer_tree.insert("", "end", text=f"📄 {f}", values=(os.path.join(path, f),))
        except PermissionError:
            pass
            
    def on_explorer_double_click(self, event):
        selection = self.explorer_tree.selection()
        if selection:
            path = self.explorer_tree.item(selection[0], "values")[0]
            if os.path.isfile(path):
                self.open_file(path)
            elif os.path.isdir(path):
                self.explorer_path_var.set(path)
                self.refresh_file_explorer()
                
    def setup_build_quick_tab(self):
        colors = GRADIENT_COLORS
        
        self.build_quick_tab = ctk.CTkFrame(self.tools_notebook, fg_color=colors["bg_secondary"])
        self.tools_notebook.add(self.build_quick_tab, text="  📦 Build  ")
        
        ctk.CTkLabel(
            self.build_quick_tab,
            text="Quick Build",
            font=("Segoe UI", 14, "bold"),
            text_color=colors["accent_green"]
        ).pack(pady=15)
        
        self.build_info = ctk.CTkTextbox(
            self.build_quick_tab,
            font=("JetBrains Mono", 10),
            fg_color=colors["bg_primary"],
            text_color=colors["text_primary"],
            height=100,
            corner_radius=8
        )
        self.build_info.pack(fill="x", padx=15, pady=10)
        
        self.build_info.insert("end", "Ready to build!\n")
        self.build_info.insert("end", "Click BUILD EXE in toolbar\n")
        self.build_info.insert("end", "or press Ctrl+Shift+B\n")
        
        build_btn = ctk.CTkButton(
            self.build_quick_tab,
            text="🔨 OPEN BUILD DIALOG",
            height=45,
            font=("Segoe UI", 12, "bold"),
            fg_color=colors["accent_green"],
            hover_color=colors["button_primary_hover"],
            command=self.open_build_dialog
        )
        build_btn.pack(fill="x", padx=20, pady=10)
        
        quick_build_btn = ctk.CTkButton(
            self.build_quick_tab,
            text="⚡ QUICK BUILD (Auto)",
            height=35,
            font=("Segoe UI", 11),
            fg_color=colors["button_secondary"],
            hover_color=colors["button_secondary_hover"],
            command=self.quick_build
        )
        quick_build_btn.pack(fill="x", padx=20, pady=5)
        
    def setup_statusbar(self):
        colors = GRADIENT_COLORS
        
        self.statusbar = ctk.CTkFrame(self.main_container, height=25, fg_color=colors["statusbar_bg"], corner_radius=0)
        self.statusbar.pack(fill="x", side="bottom")
        
        ctk.CTkLabel(self.statusbar, text="  Ready", font=("Segoe UI", 9), text_color=colors["text_secondary"]).pack(side="left")
        
        status_right = ctk.CTkFrame(self.statusbar, fg_color="transparent")
        status_right.pack(side="right")
        
        self.line_col_label = ctk.CTkLabel(status_right, text="Ln 1, Col 1  ", font=("Segoe UI", 9), text_color=colors["text_secondary"])
        self.line_col_label.pack(side="left")
        
        self.language_label = ctk.CTkLabel(status_right, text="Text  ", font=("Segoe UI", 9), text_color=colors["text_secondary"])
        self.language_label.pack(side="left")
        
    def setup_keybindings(self):
        self.bind("", lambda e: self.new_file())
        self.bind("", lambda e: self.open_file_dialog())
        self.bind("", lambda e: self.save_file())
        self.bind("", lambda e: self.on_close())
        self.bind("", lambda e: self.undo())
        self.bind("", lambda e: self.redo())
        self.bind("", lambda e: self.cut())
        self.bind("", lambda e: self.copy())
        self.bind("", lambda e: self.paste())
        self.bind("", lambda e: self.toggle_search())
        self.bind("", lambda e: self.toggle_tools_panel())
        self.bind("", lambda e: self.open_build_dialog())
        self.bind("", lambda e: self.terminal_input.focus_set())
        
    def get_language_config(self, lang):
        configs = {
            "python": {"name": "Python", "keywords": ["False","None","True","and","as","assert","async","await","break","class","continue","def","del","elif","else","except","finally","for","from","global","if","import","in","is","lambda","nonlocal","not","or","pass","raise","return","try","while","with","yield"], "types": ["int","float","str","list","dict","tuple","set","bool","bytes","object"]},
            "cpp": {"name": "C++", "keywords": ["auto","break","case","class","const","continue","default","delete","do","else","enum","extern","for","friend","goto","if","inline","namespace","new","operator","private","protected","public","return","sizeof","static","struct","switch","template","this","throw","try","typedef","union","using","virtual","void","volatile","while"], "types": ["bool","char","double","float","int","long","short","string","vector","map"]},
            "c": {"name": "C", "keywords": ["auto","break","case","char","const","continue","default","do","double","else","enum","extern","float","for","goto","if","int","long","register","return","short","signed","sizeof","static","struct","switch","typedef","union","unsigned","void","volatile","while"], "types": ["int","char","float","double","long","short","unsigned","void"]},
            "rust": {"name": "Rust", "keywords": ["as","break","const","continue","crate","else","enum","extern","false","fn","for","if","impl","in","let","loop","match","mod","move","mut","pub","ref","return","self","Self","static","struct","super","trait","true","type","unsafe","use","where","while","async","await"], "types": ["i32","i64","f32","f64","bool","char","str","String","Vec","Option","Result"]},
            "csharp": {"name": "C#", "keywords": ["abstract","as","base","bool","break","byte","case","catch","char","checked","class","const","continue","decimal","default","delegate","do","double","else","enum","event","explicit","extern","false","finally","fixed","float","for","foreach","goto","if","implicit","in","int","interface","internal","is","lock","long","namespace","new","null","object","operator","out","override","params","private","protected","public","readonly","ref","return","sbyte","sealed","short","sizeof","stackalloc","static","string","struct","switch","this","throw","true","try","typeof","uint","ulong","unchecked","unsafe","ushort","using","virtual","void","volatile","while"], "types": ["string","int","long","float","double","bool","decimal","char","byte","object","dynamic","var"]},
        }
        return configs.get(lang, {"name": "Text", "keywords": [], "types": []})
        
    def new_file(self, event=None):
        if self.modified:
            response = messagebox.askyesnocancel("Save", "Save current file?")
            if response is None: return
            if response: self.save_file()
                
        self.text_editor.delete("1.0", "end")
        self.current_file = None
        self.modified = False
        self.current_language = "text"
        self.lang_config = self.get_language_config("text")
        self.update_title()
        self.update_statusbar()
        self.update_line_numbers()
        
    def open_file_dialog(self):
        file_path = filedialog.askopenfilename(title="Open File", filetypes=[
            ("All Files", "*.*"),
            ("Python", "*.py"),
            ("C/C++", "*.cpp *.c *.h *.hpp"),
            ("C#", "*.cs"),
            ("Rust", "*.rs"),
            ("Java", "*.java"),
            ("JavaScript", "*.js"),
            ("HTML", "*.html *.htm"),
            ("XML", "*.xml"),
            ("JSON", "*.json"),
            ("Text", "*.txt"),
        ])
        if file_path:
            self.open_file(file_path)
            
    def open_file(self, file_path):
        try:
            with open(file_path, "r", encoding=self.encoding) as f:
                content = f.read()
                
            self.text_editor.delete("1.0", "end")
            self.text_editor.insert("1.0", content)
            self.current_file = file_path
            self.modified = False
            
            ext = Path(file_path).suffix.lower()
            lang_map = {
                ".py": "python", ".cpp": "cpp", ".cc": "cpp", ".cxx": "cpp",
                ".c": "c", ".h": "cpp", ".hpp": "cpp",
                ".cs": "csharp", ".rs": "rust",
                ".java": "java", ".js": "javascript",
                ".ts": "typescript", ".html": "html", ".xml": "xml",
            }
            self.current_language = lang_map.get(ext, "text")
            self.lang_config = self.get_language_config(self.current_language)
            
            self.update_title()
            self.update_statusbar()
            self.update_line_numbers()
            
        except Exception as e:
            messagebox.showerror("Error", f"Failed to open file: {str(e)}")
            
    def save_file(self, event=None):
        if self.current_file:
            try:
                content = self.text_editor.get("1.0", "end-1c")
                with open(self.current_file, "w", encoding=self.encoding) as f:
                    f.write(content)
                self.modified = False
                self.update_title()
                return True
            except Exception as e:
                messagebox.showerror("Error", f"Failed to save: {str(e)}")
        else:
            return self.save_file_as()
        return False
        
    def save_file_as(self):
        file_path = filedialog.asksaveasfilename(title="Save As", defaultextension=".txt")
        if file_path:
            self.current_file = file_path
            return self.save_file()
        return False
        
    def cut(self, event=None):
        try:
            self.clipboard = self.text_editor.get("sel.first", "sel.last")
            self.text_editor.delete("sel.first", "sel.last")
            self.modified = True
        except: pass
            
    def copy(self, event=None):
        try: self.clipboard = self.text_editor.get("sel.first", "sel.last")
        except: pass
            
    def paste(self, event=None):
        if self.clipboard:
            try: self.text_editor.delete("sel.first", "sel.last")
            except: pass
            self.text_editor.insert("insert", self.clipboard)
            self.modified = True
            
    def undo(self, event=None):
        try: self.text_editor.edit_undo(); self.modified = True
        except: pass
            
    def redo(self, event=None):
        try: self.text_editor.edit_redo(); self.modified = True
        except: pass
            
    def toggle_search(self, event=None):
        dialog = ctk.CTkInputDialog(text="Search:", title="Find", fg_color=GRADIENT_COLORS["dialog_bg"])
        search_text = dialog.get_input()
        if search_text:
            pos = self.text_editor.search(search_text, "1.0", stopindex="end")
            if pos:
                end = f"{pos}+{len(search_text)}c"
                self.text_editor.tag_add("sel", pos, end)
                self.text_editor.see(pos)
                
    def toggle_tools_panel(self, event=None):
        if self.tools_panel_visible:
            self.tools_container.pack_forget()
        else:
            self.tools_container.pack(side="right", fill="y")
        self.tools_panel_visible = not self.tools_panel_visible
        
    def open_build_dialog(self, event=None):
        if self.build_dialog is None or not self.build_dialog.winfo_exists():
            self.build_dialog = BuildDialog(self, self)
        self.build_dialog.focus()
        
    def quick_build(self):
        if not self.current_file:
            messagebox.showwarning("No File", "Open a file first!")
            return
        
        if self.modified:
            self.save_file()
        
        self.open_build_dialog()
        if self.build_dialog:
            self.build_dialog.start_build()
        
    def on_key_press(self, event):
        self.update_line_numbers()
        self.update_statusbar()
        
    def on_text_modified(self, event):
        if self.text_editor.edit_modified():
            self.modified = True
            self.text_editor.edit_modified(False)
            self.update_title()
            
    def update_ui(self):
        self.after(100, self.update_ui)
        
    def update_title(self):
        title = f"Vertex Editor - {os.path.basename(self.current_file) if self.current_file else 'Untitled'}"
        if self.modified: title += " ●"
        self.title(title)
        
    def update_statusbar(self):
        pos = self.text_editor.index("insert")
        line, col = pos.split(".")
        self.line_col_label.configure(text=f"Ln {line}, Col {int(col) + 1}  ")
        self.language_label.configure(text=f"{self.lang_config.get('name', 'Text')}  ")
        
    def update_line_numbers(self):
        lines = int(self.text_editor.index("end-1c").split(".")[0])
        line_numbers = "\n".join(str(i) for i in range(1, lines + 1))
        self.line_numbers.configure(state="normal")
        self.line_numbers.delete("1.0", "end")
        self.line_numbers.insert("1.0", line_numbers)
        self.line_numbers.configure(state="disabled")
        
    def on_close(self):
        if self.modified:
            response = messagebox.askyesnocancel("Save", "Save changes before closing?")
            if response is None: return
            if response: self.save_file()
        self.destroy()

if __name__ == "__main__":
    app = VertexEditor()
    app.mainloop()