// Everything the Options, Window and Help menus do: how the editor looks and // how its windows are arranged. package app import ( "fmt" "path/filepath" "strings" "codeberg.org/turbo-editors/turbo-core/editor" "codeberg.org/turbo-editors/turbo-core/theme" "codeberg.org/turbo-editors/turbo-core/ui" "codeberg.org/turbo-editors/turbo-core/version" ) // ToggleLineNumbers shows or hides the gutter in the front window. func (a *App) ToggleLineNumbers() { a.withView(func(v *editor.View) { v.SetLineNumbers(!v.LineNumbers()) }) } // ChooseTheme offers the available themes and applies the one picked. func (a *App) ChooseTheme() { names := theme.Available(a.profile.ThemeDir()) dialog, list := NewChoiceDialog("Theme", names, indexOf(names, a.themeName), a.screenRect()) a.pushModal(dialog, func(result ui.Result) { if result == ui.ResultOK && list.Selected() >= 0 { a.setTheme(names[list.Selected()]) a.rememberTheme(a.themeName) a.Message("Theme: " + a.theme.Name()) } }) } // NextWindow brings the window behind the front one forward. func (a *App) NextWindow() { a.desktop.Next() } // TileWindows lays every window out side by side. func (a *App) TileWindows() { a.desktop.Tile() } // CascadeWindows stacks the windows with their titles showing. func (a *App) CascadeWindows() { a.desktop.Cascade() } // MaximizeWindow gives the front window the whole desktop, or puts it back // where it was if it already has it — the same toggle as the frame's box, so // the menu and the button never disagree. func (a *App) MaximizeWindow() { if window := a.desktop.Active(); window != nil { a.desktop.ToggleMaximize(window) } } // ListWindows offers the open windows and brings the chosen one forward. func (a *App) ListWindows() { windows := a.desktop.Windows() if len(windows) == 0 { return } names := make([]string, len(windows)) for i, window := range windows { names[i] = fmt.Sprintf("%d. %s", window.Number(), window.Title()) } dialog, list := NewChoiceDialog("Windows", names, len(windows)-1, a.screenRect()) a.pushModal(dialog, func(result ui.Result) { if result == ui.ResultOK && list.Selected() >= 0 { a.desktop.Focus(windows[list.Selected()]) } }) } // ShowLanguageStatus reports what the language server is doing, in enough // detail to explain a completion that produced nothing. func (a *App) ShowLanguageStatus() { a.ShowMessage("Language server", strings.Join(a.languageReport(), "\n")) } // languageReport builds the lines the status box shows. func (a *App) languageReport() []string { report := a.language.Report() lines := []string{report.Status} if report.ServerPath != "" { lines = append(lines, "Server: "+report.ServerPath) } if report.Root != "" { lines = append(lines, "Root: "+report.Root) } if !report.Ready { return append(lines, "", "Editing and colouring work without it;", "only completion needs a language server.") } return append(lines, a.currentFileReport()...) } // currentFileReport says what the server knows about the file being edited, // which is where an unexplained empty completion is usually explained. func (a *App) currentFileReport() []string { view := a.activeView() if view == nil { return nil } path := view.Buffer().Path() if path == "" { return []string{"", "This window has no file yet, so the server", "has nothing to answer about. Save it first."} } lines := []string{"File: " + filepath.Base(path)} if !a.language.Knows(path) { return append(lines, "", "The server has not been told about it.") } if diagnostic, ok := a.language.FirstError(path); ok { return append(lines, "Problem: "+diagnostic.Message, "", "Completion needs the file's package to compile.") } return lines } // ShowAbout shows the About box. func (a *App) ShowAbout() { a.ShowMessage("About", aboutText(a.profile.Name, a.profile.Language, version.Current(), a.theme.Name())) } // aboutText is what the About box says. // // The language comes from the profile rather than being written into this // string: this box belongs to the library, and the library is not for Go. It // said "an editor for Go" in every editor built on it until Turbo Rust showed // somebody the wrong sentence. // // A fact the build did not record is **left out rather than shown empty**: a // binary from `go install …@v0.2.0` knows its version and nothing else, and a // blank "Commit:" line would say only that the editor failed to fill it in. func aboutText(name, language string, info version.Info, themeName string) string { lines := []string{ name + " " + info.Number, "", "A Turbo C-style editor for " + language + ",", "written in Go.", "", } if info.Commit != "" { lines = append(lines, "Commit: "+info.Commit) } if built := info.BuiltAt(); built != "" { lines = append(lines, "Built: "+built) } return strings.Join(append(lines, "Theme: "+themeName), "\n") } // ShowKeyboardHelp lists the keys worth knowing. func (a *App) ShowKeyboardHelp() { a.ShowMessage("Keyboard", strings.Join([]string{ "F2 Save F3 Open F4 New", "F6 Next window F7 Find next", "F10 Menu F12 Go to definition", "Ctrl-F Find Ctrl-G Go to line", "Ctrl-Space Completion", "Alt-1…9 Window Alt-X Exit", }, "\n")) } // indexOf returns where a name sits in a list, or zero when it is absent. func indexOf(names []string, wanted string) int { for i, name := range names { if name == wanted { return i } } return 0 }