| 🛟 Updated. 28d5985 k33g 17h ago | 1 | // Everything the Options, Window and Help menus do: how the editor looks and |
| 2 | // how its windows are arranged. |
| 3 | package app |
| 4 | |
| 5 | import ( |
| 6 | "fmt" |
| 7 | "path/filepath" |
| 8 | "strings" |
| 9 | |
| 📦 Turbo Core f3ade8d k33g 10h ago | 10 | "rickub.com/turbo-editors/turbo-core/editor" |
| 11 | "rickub.com/turbo-editors/turbo-core/theme" |
| 12 | "rickub.com/turbo-editors/turbo-core/ui" |
| 13 | "rickub.com/turbo-editors/turbo-core/version" |
| 🛟 Updated. 28d5985 k33g 17h ago | 14 | ) |
| 15 | |
| 16 | // ToggleLineNumbers shows or hides the gutter in the front window. |
| 17 | func (a *App) ToggleLineNumbers() { |
| 18 | a.withView(func(v *editor.View) { v.SetLineNumbers(!v.LineNumbers()) }) |
| 19 | } |
| 20 | |
| 21 | // ChooseTheme offers the available themes and applies the one picked. |
| 22 | func (a *App) ChooseTheme() { |
| 23 | names := theme.Available(a.profile.ThemeDir()) |
| 24 | dialog, list := NewChoiceDialog("Theme", names, indexOf(names, a.themeName), a.screenRect()) |
| 25 | |
| 26 | a.pushModal(dialog, func(result ui.Result) { |
| 27 | if result == ui.ResultOK && list.Selected() >= 0 { |
| 28 | a.setTheme(names[list.Selected()]) |
| 29 | a.rememberTheme(a.themeName) |
| 30 | a.Message("Theme: " + a.theme.Name()) |
| 31 | } |
| 32 | }) |
| 33 | } |
| 34 | |
| 35 | // NextWindow brings the window behind the front one forward. |
| 36 | func (a *App) NextWindow() { a.desktop.Next() } |
| 37 | |
| 38 | // TileWindows lays every window out side by side. |
| 39 | func (a *App) TileWindows() { a.desktop.Tile() } |
| 40 | |
| 41 | // CascadeWindows stacks the windows with their titles showing. |
| 42 | func (a *App) CascadeWindows() { a.desktop.Cascade() } |
| 43 | |
| 44 | // MaximizeWindow gives the front window the whole desktop, or puts it back |
| 45 | // where it was if it already has it — the same toggle as the frame's box, so |
| 46 | // the menu and the button never disagree. |
| 47 | func (a *App) MaximizeWindow() { |
| 48 | if window := a.desktop.Active(); window != nil { |
| 49 | a.desktop.ToggleMaximize(window) |
| 50 | } |
| 51 | } |
| 52 | |
| 53 | // ListWindows offers the open windows and brings the chosen one forward. |
| 54 | func (a *App) ListWindows() { |
| 55 | windows := a.desktop.Windows() |
| 56 | if len(windows) == 0 { |
| 57 | return |
| 58 | } |
| 59 | |
| 60 | names := make([]string, len(windows)) |
| 61 | for i, window := range windows { |
| 62 | names[i] = fmt.Sprintf("%d. %s", window.Number(), window.Title()) |
| 63 | } |
| 64 | |
| 65 | dialog, list := NewChoiceDialog("Windows", names, len(windows)-1, a.screenRect()) |
| 66 | a.pushModal(dialog, func(result ui.Result) { |
| 67 | if result == ui.ResultOK && list.Selected() >= 0 { |
| 68 | a.desktop.Focus(windows[list.Selected()]) |
| 69 | } |
| 70 | }) |
| 71 | } |
| 72 | |
| 73 | // ShowLanguageStatus reports what the language server is doing, in enough |
| 74 | // detail to explain a completion that produced nothing. |
| 75 | func (a *App) ShowLanguageStatus() { |
| 76 | a.ShowMessage("Language server", strings.Join(a.languageReport(), "\n")) |
| 77 | } |
| 78 | |
| 79 | // languageReport builds the lines the status box shows. |
| 80 | func (a *App) languageReport() []string { |
| 81 | report := a.language.Report() |
| 82 | lines := []string{report.Status} |
| 83 | |
| 84 | if report.ServerPath != "" { |
| 85 | lines = append(lines, "Server: "+report.ServerPath) |
| 86 | } |
| 87 | if report.Root != "" { |
| 88 | lines = append(lines, "Root: "+report.Root) |
| 89 | } |
| 90 | if !report.Ready { |
| 91 | return append(lines, "", "Editing and colouring work without it;", "only completion needs a language server.") |
| 92 | } |
| 93 | return append(lines, a.currentFileReport()...) |
| 94 | } |
| 95 | |
| 96 | // currentFileReport says what the server knows about the file being edited, |
| 97 | // which is where an unexplained empty completion is usually explained. |
| 98 | func (a *App) currentFileReport() []string { |
| 99 | view := a.activeView() |
| 100 | if view == nil { |
| 101 | return nil |
| 102 | } |
| 103 | path := view.Buffer().Path() |
| 104 | if path == "" { |
| 105 | return []string{"", "This window has no file yet, so the server", "has nothing to answer about. Save it first."} |
| 106 | } |
| 107 | |
| 108 | lines := []string{"File: " + filepath.Base(path)} |
| 109 | if !a.language.Knows(path) { |
| 110 | return append(lines, "", "The server has not been told about it.") |
| 111 | } |
| 112 | if diagnostic, ok := a.language.FirstError(path); ok { |
| 113 | return append(lines, "Problem: "+diagnostic.Message, "", |
| 114 | "Completion needs the file's package to compile.") |
| 115 | } |
| 116 | return lines |
| 117 | } |
| 118 | |
| 119 | // ShowAbout shows the About box. |
| 120 | func (a *App) ShowAbout() { |
| 121 | a.ShowMessage("About", aboutText(a.profile.Name, a.profile.Language, version.Current(), a.theme.Name())) |
| 122 | } |
| 123 | |
| 124 | // aboutText is what the About box says. |
| 125 | // |
| 126 | // The language comes from the profile rather than being written into this |
| 127 | // string: this box belongs to the library, and the library is not for Go. It |
| 128 | // said "an editor for Go" in every editor built on it until Turbo Rust showed |
| 129 | // somebody the wrong sentence. |
| 130 | // |
| 131 | // A fact the build did not record is **left out rather than shown empty**: a |
| 132 | // binary from `go install …@v0.2.0` knows its version and nothing else, and a |
| 133 | // blank "Commit:" line would say only that the editor failed to fill it in. |
| 134 | func aboutText(name, language string, info version.Info, themeName string) string { |
| 135 | lines := []string{ |
| 136 | name + " " + info.Number, |
| 137 | "", |
| 138 | "A Turbo C-style editor for " + language + ",", |
| 139 | "written in Go.", |
| 140 | "", |
| 141 | } |
| 142 | if info.Commit != "" { |
| 143 | lines = append(lines, "Commit: "+info.Commit) |
| 144 | } |
| 145 | if built := info.BuiltAt(); built != "" { |
| 146 | lines = append(lines, "Built: "+built) |
| 147 | } |
| 148 | return strings.Join(append(lines, "Theme: "+themeName), "\n") |
| 149 | } |
| 150 | |
| 151 | // ShowKeyboardHelp lists the keys worth knowing. |
| 152 | func (a *App) ShowKeyboardHelp() { |
| 153 | a.ShowMessage("Keyboard", strings.Join([]string{ |
| 154 | "F2 Save F3 Open F4 New", |
| 155 | "F6 Next window F7 Find next", |
| 156 | "F10 Menu F12 Go to definition", |
| 157 | "Ctrl-F Find Ctrl-G Go to line", |
| 158 | "Ctrl-Space Completion", |
| 159 | "Alt-1…9 Window Alt-X Exit", |
| 160 | }, "\n")) |
| 161 | } |
| 162 | |
| 163 | // indexOf returns where a name sits in a list, or zero when it is absent. |
| 164 | func indexOf(names []string, wanted string) int { |
| 165 | for i, name := range names { |
| 166 | if name == wanted { |
| 167 | return i |
| 168 | } |
| 169 | } |
| 170 | return 0 |
| 171 | } |