import { useSyncExternalStore, useCallback } from "react"; export interface EditorTab { path: string; name: string; content: string; savedContent: string; language: string; } export interface SearchResult { filePath: string; fileName: string; lineNumber: number; lineContent: string; matchStart: number; matchEnd: number; } export interface EditorSettings { fontSize: number; fontFamily: string; tabSize: number; wordWrap: boolean; autoSave: boolean; theme: "dark" | "light"; customCssPath: string | null; } interface EditorStore { tabs: EditorTab[]; activeTabPath: string | null; workspacePath: string | null; sidebarVisible: boolean; terminalVisible: boolean; sidebarView: "files" | "search"; searchQuery: string; searchResults: SearchResult[]; searchCaseSensitive: boolean; searchRegex: boolean; splitEditorPath: string | null; recentWorkspaces: string[]; fileTreeFilter: string; cursorLine: number; cursorCol: number; settings: EditorSettings; } const defaultSettings: EditorSettings = { fontSize: 14, fontFamily: "'JetBrains Mono', 'Fira Code', monospace", tabSize: 2, wordWrap: false, autoSave: false, theme: "dark", customCssPath: null, }; function loadSettings(): EditorSettings { try { const stored = localStorage.getItem("editor-settings"); if (stored) return { ...defaultSettings, ...JSON.parse(stored) }; } catch { /* ignore */ } return defaultSettings; } function loadRecentWorkspaces(): string[] { try { const stored = localStorage.getItem("recent-workspaces"); if (stored) return JSON.parse(stored); } catch { /* ignore */ } return []; } let state: EditorStore = { tabs: [], activeTabPath: null, workspacePath: null, sidebarVisible: true, terminalVisible: false, sidebarView: "files", searchQuery: "", searchResults: [], searchCaseSensitive: false, searchRegex: false, splitEditorPath: null, recentWorkspaces: loadRecentWorkspaces(), fileTreeFilter: "", cursorLine: 1, cursorCol: 1, settings: loadSettings(), }; const listeners = new Set<() => void>(); function emit() { listeners.forEach((l) => l()); } function getSnapshot() { return state; } function subscribe(listener: () => void) { listeners.add(listener); return () => listeners.delete(listener); } export function useStore() { return useSyncExternalStore(subscribe, getSnapshot); } export function useStoreSelector(selector: (s: EditorStore) => T): T { const select = useCallback(() => selector(getSnapshot()), [selector]); return useSyncExternalStore(subscribe, select); } export const actions = { openFile(path: string, name: string, content: string, language: string) { const existing = state.tabs.find((t) => t.path === path); if (existing) { state = { ...state, activeTabPath: path }; } else { state = { ...state, tabs: [...state.tabs, { path, name, content, savedContent: content, language }], activeTabPath: path, }; } emit(); }, closeTab(path: string) { const tabs = state.tabs.filter((t) => t.path !== path); let activeTabPath = state.activeTabPath; if (activeTabPath === path) { const idx = state.tabs.findIndex((t) => t.path === path); activeTabPath = tabs[Math.min(idx, tabs.length - 1)]?.path ?? null; } // Also close split if it was showing this file let splitEditorPath = state.splitEditorPath; if (splitEditorPath === path) { splitEditorPath = null; } state = { ...state, tabs, activeTabPath, splitEditorPath }; emit(); }, setActiveTab(path: string) { state = { ...state, activeTabPath: path }; emit(); }, updateContent(path: string, content: string) { state = { ...state, tabs: state.tabs.map((t) => (t.path === path ? { ...t, content } : t)), }; emit(); }, markSaved(path: string) { state = { ...state, tabs: state.tabs.map((t) => t.path === path ? { ...t, savedContent: t.content } : t ), }; emit(); }, setWorkspace(path: string | null) { state = { ...state, workspacePath: path }; if (path) { const recents = [path, ...state.recentWorkspaces.filter((w) => w !== path)].slice(0, 10); state = { ...state, recentWorkspaces: recents }; localStorage.setItem("recent-workspaces", JSON.stringify(recents)); } emit(); }, toggleSidebar() { state = { ...state, sidebarVisible: !state.sidebarVisible }; emit(); }, toggleTerminal() { state = { ...state, terminalVisible: !state.terminalVisible }; emit(); }, setSidebarView(view: "files" | "search") { state = { ...state, sidebarView: view, sidebarVisible: true }; emit(); }, setSearchQuery(query: string) { state = { ...state, searchQuery: query }; emit(); }, setSearchResults(results: SearchResult[]) { state = { ...state, searchResults: results }; emit(); }, setSearchCaseSensitive(value: boolean) { state = { ...state, searchCaseSensitive: value }; emit(); }, setSearchRegex(value: boolean) { state = { ...state, searchRegex: value }; emit(); }, setSplitEditor(path: string | null) { state = { ...state, splitEditorPath: path }; emit(); }, setFileTreeFilter(filter: string) { state = { ...state, fileTreeFilter: filter }; emit(); }, reorderTabs(fromIndex: number, toIndex: number) { const tabs = [...state.tabs]; const [moved] = tabs.splice(fromIndex, 1); tabs.splice(toIndex, 0, moved); state = { ...state, tabs }; emit(); }, reloadFileContent(path: string, content: string) { state = { ...state, tabs: state.tabs.map((t) => t.path === path ? { ...t, content, savedContent: content } : t ), }; emit(); }, setCursor(line: number, col: number) { state = { ...state, cursorLine: line, cursorCol: col }; emit(); }, updateTabLanguage(path: string, language: string) { state = { ...state, tabs: state.tabs.map((t) => (t.path === path ? { ...t, language } : t)), }; emit(); }, updateSettings(partial: Partial) { const settings = { ...state.settings, ...partial }; state = { ...state, settings }; localStorage.setItem("editor-settings", JSON.stringify(settings)); emit(); }, getState() { return state; }, };