/
smalloff
/
goruner
Обзор
Документация
Войти
/
smalloff
/
goruner
Код
Задачи
Вики
Пакеты
0
Релизы
1
Аналитика
Безопасность
main
app.go
251 строка
6 KB
smalloff
bug fixes
16 янв 2026, 12:58
16 янв 2026, 12:58
0cc479f
Код
Авторство
О чём код?
package main import ( "context" "fmt" "goruner/internal/config" "goruner/internal/notifier" "goruner/internal/tester" "os" "path/filepath" "strings" "sync" "github.com/fsnotify/fsnotify" wailsRuntime "github.com/wailsapp/wails/v2/pkg/runtime" ) // App defines the main Wails application controller. type App struct { ctx context.Context watcher *fsnotify.Watcher mu sync.Mutex isPaused bool } // NewApp initializes a new App instance. func NewApp() *App { return &App{isPaused: false} } // startup is called by Wails when the application starts. func (a *App) startup(ctx context.Context) { a.ctx = ctx a.initWatcher() cfg := config.Load() if cfg.AlwaysOnTop { wailsRuntime.WindowSetAlwaysOnTop(a.ctx, true) } } // getProjectRoot determines the working directory for tests. // Returns the configured ProjectPath if valid, otherwise the current working directory. func (a *App) getProjectRoot() string { cfg := config.Load() if cfg.ProjectPath != "" { if info, err := os.Stat(cfg.ProjectPath); err == nil && info.IsDir() { return cfg.ProjectPath } } cwd, _ := os.Getwd() return cwd } // initWatcher configures the filesystem observer. func (a *App) initWatcher() { w, err := fsnotify.NewWatcher() if err != nil { return } a.watcher = w go a.watchLoop() a.refreshWatcher() } // watchLoop processes filesystem events. func (a *App) watchLoop() { for { select { case event, ok := <-a.watcher.Events: if !ok { return } a.mu.Lock() paused := a.isPaused a.mu.Unlock() if paused { continue } root := a.getProjectRoot() cfg := config.Load() // Auto-watch new directories if event.Op&fsnotify.Create != 0 { info, err := os.Stat(event.Name) if err == nil && info.IsDir() && !cfg.IsExcluded(event.Name, root) { _ = a.watcher.Add(event.Name) } } // Trigger tests on Go file modifications if event.Op&(fsnotify.Write|fsnotify.Create|fsnotify.Rename) != 0 { if strings.HasSuffix(strings.ToLower(event.Name), ".go") && cfg.AutoWatch && !cfg.IsExcluded(event.Name, root) { wailsRuntime.EventsEmit(a.ctx, "trigger_test", "File changed: "+filepath.Base(event.Name)) } } case err, ok := <-a.watcher.Errors: if !ok { return } fmt.Printf("[Watcher] Error: %v\n", err) } } } // RunTests executes Go tests and processes results for the UI. func (a *App) RunTests() string { cfg := config.Load() root := a.getProjectRoot() pkgs, err := tester.DiscoverTests(root) if err != nil { return fmt.Sprintf("Discovery Error: %v", err) } out, err := tester.RunTests(a.ctx, root, pkgs, cfg.ShowPassed, cfg.Lang) if err != nil { return fmt.Sprintf("Execution Error: %v\nOutput: %s", err, out) } isError := tester.IsFailureOutput(out, cfg.Lang) if cfg.ShowNotifications { if !cfg.NotifyOnlyOnFailure || isError { go a.sendNotify(out, isError, cfg.Lang) } } if isError && cfg.AutoCopyErrors { a.copyErrorsToClipboard(out) } return out } func (a *App) sendNotify(output string, isError bool, lang string) { title := "Go Test Runner" var msg string if lang == "ru" { if isError { msg = "❌ Тесты провалены!" } else { msg = "✅ Все тесты пройдены!" } } else { if isError { msg = "❌ Tests failed!" } else { msg = "✅ All tests passed!" } } notifier.Notify(title, msg) } func (a *App) copyErrorsToClipboard(output string) { lines := strings.Split(output, "\n") var errLines []string for _, l := range lines { trimmed := strings.TrimSpace(l) if trimmed == "" { continue } isError := strings.HasPrefix(trimmed, "#") || strings.Contains(l, ".go:") || strings.Contains(l, "FAIL") || strings.Contains(strings.ToLower(l), "error:") || strings.Contains(l, "panic:") if isError { errLines = append(errLines, l) } } if len(errLines) > 0 { _ = wailsRuntime.ClipboardSetText(a.ctx, strings.Join(errLines, "\n")) } } func (a *App) refreshWatcher() { cfg := config.Load() root := a.getProjectRoot() _ = filepath.Walk(root, func(path string, info os.FileInfo, err error) error { if err != nil || !info.IsDir() { return nil } if cfg.IsExcluded(path, root) { return filepath.SkipDir } _ = a.watcher.Add(path) return nil }) } // --- Wails Bindings --- // SelectProjectDir opens a native dialog to choose the project folder. func (a *App) SelectProjectDir() string { selection, err := wailsRuntime.OpenDirectoryDialog(a.ctx, wailsRuntime.OpenDialogOptions{ Title: "Select Project Directory", }) if err != nil { return "" } return selection } // GetDiscoveredTests returns a list of packages that contain tests. func (a *App) GetDiscoveredTests() []string { root := a.getProjectRoot() pkgs, _ := tester.DiscoverTests(root) return pkgs } // TogglePause switches the file watcher's active state. func (a *App) TogglePause() bool { a.mu.Lock() defer a.mu.Unlock() a.isPaused = !a.isPaused return a.isPaused } // GetConfig retrieves the current configuration instance. func (a *App) GetConfig() *config.Config { return config.Load() } // SaveConfig updates and persists application settings. func (a *App) SaveConfig(newCfg config.Config) { oldCfg := config.Load() pathChanged := oldCfg.ProjectPath != newCfg.ProjectPath _ = config.Save(&newCfg) wailsRuntime.WindowSetAlwaysOnTop(a.ctx, newCfg.AlwaysOnTop) if pathChanged { if a.watcher != nil { a.watcher.Close() } a.initWatcher() // Trigger a test run on path change wailsRuntime.EventsEmit(a.ctx, "trigger_test", "Project path changed") } } // Quit terminates the application gracefully. func (a *App) Quit() { wailsRuntime.Quit(a.ctx) }