package main import ( "flag" "fmt" "os" tea "github.com/charmbracelet/bubbletea" ) func main() { var filePath string var configPath string var writeConfig bool flag.StringVar(&filePath, "f", "", "File to edit") flag.StringVar(&configPath, "config", "", "Path to config file (overrides auto-search)") flag.BoolVar(&writeConfig, "write-config", false, "Write default config to standard location and exit") flag.Parse() // Позиционный аргумент: grimoir file.txt или grimoir https://... if filePath == "" && flag.NArg() > 0 { filePath = flag.Arg(0) } // Загружаем конфиг var cfg Config if configPath != "" { data, err := os.ReadFile(configPath) if err != nil { fmt.Fprintf(os.Stderr, "Error reading config %s: %v\n", configPath, err) os.Exit(1) } cfg = DefaultConfig() if err := parseConfig(data, &cfg); err != nil { fmt.Fprintf(os.Stderr, "Error parsing config %s: %v\n", configPath, err) os.Exit(1) } cfg.LoadedFrom = configPath } else { cfg = LoadConfig() } // --write-config if writeConfig { paths := ConfigPaths() target := "" for _, p := range paths { if p != paths[0] { target = p break } } if target == "" { target = "grimoir.conf" } if err := WriteDefaultConfig(target); err != nil { fmt.Fprintf(os.Stderr, "Error writing config: %v\n", err) os.Exit(1) } fmt.Printf("Default config written to: %s\n", target) os.Exit(0) } // === Если файл не передан — показываем стартовое меню === if filePath == "" { result := RunMenu() if result == nil { // Пользователь закрыл меню без выбора os.Exit(0) } switch result.Action { case menuQuit: os.Exit(0) case menuHelp: // Открываем редактор с пустым файлом и сразу показываем help runEditor("untitled.md", cfg, true) return case menuNewFile: // Гарантированно пустой буфер — даже если untitled.md уже существует // на диске от прошлой сессии, не подгружаем его содержимое. runNewEditor(cfg) return case menuOpenFile: filePath = result.FilePath case menuFetchURL: filePath = result.URL } } // === URL — скачиваем и конвертируем === if IsURL(filePath) { fmt.Fprintf(os.Stderr, "Fetching: %s\n", filePath) result, err := FetchAndConvert(filePath) if err != nil { fmt.Fprintf(os.Stderr, "Error fetching URL: %v\n", err) os.Exit(1) } savedPath, err := SaveFetchResult(result, "") if err != nil { fmt.Fprintf(os.Stderr, "Error saving file: %v\n", err) os.Exit(1) } fmt.Fprintf(os.Stderr, "Saved as: %s\n", savedPath) filePath = savedPath } runEditor(filePath, cfg, false) } // runNewEditor запускает редактор с гарантированно пустым буфером, // не читая существующий файл с диска (используется для "Новый файл" в меню). // Имя файла остаётся "untitled.md" — пользователь сохранит его явным :w/:saveas, // что не перезапишет существующий файл, так как save() запросит подтверждение // только при явном указании того же имени. func runNewEditor(cfg Config) { model := NewEditor("untitled.md", "md", "", cfg) p := tea.NewProgram(model, tea.WithAltScreen()) if _, err := p.Run(); err != nil { fmt.Fprintf(os.Stderr, "Error: %v\n", err) os.Exit(1) } } // runEditor загружает файл и запускает редактор. // showHelp=true открывает справку сразу при старте. func runEditor(filePath string, cfg Config, showHelp bool) { content, err := LoadFile(filePath) if err != nil && !os.IsNotExist(err) { fmt.Fprintf(os.Stderr, "Error loading file: %v\n", err) os.Exit(1) } fileType := DetectFileType(filePath) model := NewEditor(filePath, fileType, content, cfg) if showHelp { model.showHelp = true } p := tea.NewProgram(model, tea.WithAltScreen()) if _, err := p.Run(); err != nil { fmt.Fprintf(os.Stderr, "Error: %v\n", err) os.Exit(1) } }