diff options
Diffstat (limited to 'src/stores/useTabsStore.ts')
| -rw-r--r-- | src/stores/useTabsStore.ts | 152 |
1 files changed, 152 insertions, 0 deletions
diff --git a/src/stores/useTabsStore.ts b/src/stores/useTabsStore.ts new file mode 100644 index 0000000..eb9a03a --- /dev/null +++ b/src/stores/useTabsStore.ts @@ -0,0 +1,152 @@ +import { create } from 'zustand' +import type { Tab } from '@/types' +import { readFile, writeFile, saveDialog, pickFile } from '@/lib/tauri' +import { detectLanguage } from '@/lib/languageMap' + +interface TabsState { + tabs: Tab[] + activeTabId: string | null + cursorLine: number + cursorCol: number + savingId: string | null + statusMessage: string | null + untitledCounter: number + + openFile: (path: string) => Promise<void> + pickAndOpenFile: () => Promise<void> + createUntitledFile: () => void + closeTab: (id: string) => void + setActiveTab: (id: string) => void + updateContent: (id: string, content: string) => void + saveFile: (id: string) => Promise<void> + setCursorPosition: (line: number, col: number) => void + clearStatusMessage: () => void +} + +function isUntitled(id: string) { + return id.startsWith('untitled://') +} + +export const useTabsStore = create<TabsState>((set, get) => ({ + tabs: [], + activeTabId: null, + cursorLine: 1, + cursorCol: 1, + savingId: null, + statusMessage: null, + untitledCounter: 0, + + openFile: async (path: string) => { + const { tabs } = get() + const existing = tabs.find((t) => t.path === path) + if (existing) { + set({ activeTabId: existing.id }) + return + } + + try { + const content = await readFile(path) + const name = path.split(/[\\/]/).pop() ?? 'untitled' + const id = path + const tab: Tab = { + id, + path, + name, + language: detectLanguage(name), + content, + modified: false, + } + set({ tabs: [...tabs, tab], activeTabId: id }) + } catch (err) { + console.error('Failed to open file:', err) + } + }, + + pickAndOpenFile: async () => { + const path = await pickFile() + if (path) get().openFile(path) + }, + + createUntitledFile: () => { + const { tabs, untitledCounter } = get() + const n = untitledCounter + 1 + const name = `Untitled-${n}` + const id = `untitled://${n}` + const tab: Tab = { + id, + path: id, + name, + language: 'plaintext', + content: '', + modified: false, + } + set({ tabs: [...tabs, tab], activeTabId: id, untitledCounter: n }) + }, + + closeTab: (id: string) => { + const { tabs, activeTabId } = get() + const newTabs = tabs.filter((t) => t.id !== id) + let newActive = activeTabId + if (activeTabId === id) { + const idx = tabs.findIndex((t) => t.id === id) + newActive = newTabs[Math.min(idx, newTabs.length - 1)]?.id ?? null + } + set({ tabs: newTabs, activeTabId: newActive }) + }, + + setActiveTab: (id: string) => { + set({ activeTabId: id }) + }, + + updateContent: (id: string, content: string) => { + set((state) => ({ + tabs: state.tabs.map((t) => + t.id === id ? { ...t, content, modified: true } : t + ), + })) + }, + + saveFile: async (id: string) => { + const { tabs } = get() + const tab = tabs.find((t) => t.id === id) + if (!tab) return + + set({ savingId: id, statusMessage: null }) + + try { + let targetPath = tab.path + + if (isUntitled(targetPath)) { + const picked = await saveDialog(tab.name) + if (!picked) { + set({ savingId: null }) + return + } + targetPath = picked + } + + await writeFile(targetPath, tab.content) + const name = targetPath.split(/[\\/]/).pop() ?? tab.name + set({ + tabs: tabs.map((t) => + t.id === id + ? { ...t, path: targetPath, name, language: detectLanguage(name), modified: false } + : t + ), + savingId: null, + statusMessage: 'Saved', + }) + } catch (err) { + set({ savingId: null, statusMessage: 'Save failed' }) + console.error('Failed to save file:', err) + } + }, + + setCursorPosition: (line: number, col: number) => { + set({ cursorLine: line, cursorCol: col }) + }, + + clearStatusMessage: () => { + set({ statusMessage: null }) + }, +})) |