1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
|
// 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"
"rickub.com/turbo-editors/turbo-core/editor"
"rickub.com/turbo-editors/turbo-core/theme"
"rickub.com/turbo-editors/turbo-core/ui"
"rickub.com/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
}
|