turbo-editors/turbo-corepublic Fork 0
28d59854361aeda8541d853093e732126f3d7bff
Commits
Clone
git clone https://git.rickub.com/turbo-editors/turbo-core.git
git clone ssh://git@rickub.com/turbo-editors/turbo-core.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

menus.go · 194 lines · 8.5 KBGo Blame HistoryRaw
🛟 Updated. 28d5985 k33g 16h ago1package app
2
3import "codeberg.org/turbo-editors/turbo-core/ui"
4
5// buildMenus returns the editor's menu bar: File, Edit, Search, Run, Options,
6// Window and Help, laid out the way Turbo C laid its own out, with Snippets,
7// the editor's own toolchain menu, and whatever menus the project's tools file
8// asks for.
9//
10// Every item is wired straight to a method of the app, and the ones that only
11// make sense with a file open say so through Enabled, so the menu greys them
12// out instead of doing nothing when chosen.
13//
14// allMenus is where the order lives, because the bar is rebuilt from it every
15// time the tools file changes.
16func (a *App) buildMenus() *ui.MenuBar {
17 return ui.NewMenuBar(a.allMenus()...)
18}
19
20// hasWindow reports whether there is a file open to act on.
21func (a *App) hasWindow() bool { return a.desktop.Active() != nil }
22
23func (a *App) fileMenu() *ui.Menu {
24 return &ui.Menu{Label: "~F~ile", Items: []*ui.MenuItem{
25 {Label: "~N~ew", Shortcut: "F4", Action: a.NewFile},
26 {Label: "~O~pen…", Shortcut: "F3", Action: a.OpenFile},
27 {Label: "~S~ave", Shortcut: "F2", Action: a.SaveFile, Enabled: a.hasWindow},
28 {Label: "Save ~a~s…", Action: a.SaveFileAs, Enabled: a.hasWindow},
29 {Separator: true},
30 {Label: "~C~lose", Shortcut: "Ctrl-W", Action: a.CloseFile, Enabled: a.hasWindow},
31 {Separator: true},
32 {Label: "E~x~it", Shortcut: "Alt-X", Action: a.Quit},
33 }}
34}
35
36func (a *App) editMenu() *ui.Menu {
37 return &ui.Menu{Label: "~E~dit", Items: []*ui.MenuItem{
38 {Label: "~U~ndo", Shortcut: "Ctrl-Z", Action: a.Undo, Enabled: a.hasWindow},
39 // Ctrl-R, not the Ctrl-Y it used to be: Ctrl-Y is Turbo C's
40 // delete-line, and between an editor that looks like Turbo C and a
41 // habit picked up here, the first has the better claim. Ctrl-Shift-Z
42 // is not an option — a terminal delivers it as plain Ctrl-Z.
43 {Label: "~R~edo", Shortcut: "Ctrl-R", Action: a.Redo, Enabled: a.hasWindow},
44 {Separator: true},
45 {Label: "~I~nsert line", Shortcut: "Ctrl-N", Action: a.InsertLine, Enabled: a.hasWindow},
46 {Label: "~D~elete line", Shortcut: "Ctrl-Y", Action: a.DeleteLine, Enabled: a.hasWindow},
47 {Separator: true},
48 {Label: "Cu~t~", Shortcut: "Shift-Del", Action: a.Cut, Enabled: a.hasWindow},
49 {Label: "~C~opy", Shortcut: "Ctrl-Ins", Action: a.Copy, Enabled: a.hasWindow},
50 {Label: "~P~aste", Shortcut: "Shift-Ins", Action: a.Paste, Enabled: a.hasWindow},
51 {Separator: true},
52 {Label: "Select ~a~ll", Shortcut: "Ctrl-A", Action: a.SelectAll, Enabled: a.hasWindow},
53 }}
54}
55
56func (a *App) searchMenu() *ui.Menu {
57 return &ui.Menu{Label: "~S~earch", Items: []*ui.MenuItem{
58 {Label: "~F~ind…", Shortcut: "Ctrl-F", Action: a.Find, Enabled: a.hasWindow},
59 {Label: "Find ~n~ext", Shortcut: "F7", Action: a.FindNext, Enabled: a.hasWindow},
60 {Label: "Find ~p~revious", Shortcut: "Shift-F7", Action: a.FindPrevious, Enabled: a.hasWindow},
61 {Separator: true},
62 {Label: "~G~o to line…", Shortcut: "Ctrl-G", Action: a.GoToLine, Enabled: a.hasWindow},
63 }}
64}
65
66// agentMenu lists the coding agents the project's acp.toml knows about, one
67// window each.
68//
69// It is on the bar whether or not any agent is configured, because that is
70// where Create agents file has to be reachable from — the same reason the
71// toolchain menu stays fixed even in a project with no tools file.
72//
73// A file that cannot be read shows the error as a greyed line rather than an
74// empty menu: a silent drop looks exactly like having no agents.
75func (a *App) agentMenu() *ui.Menu {
76 menu := &ui.Menu{Label: "~A~gent"}
77 menu.OnOpen = func() { menu.Items = a.agentItems() }
78 menu.Items = a.agentItems()
79 return menu
80}
81
82// agentItems builds the menu's contents, which are read from the file every
83// time it drops down so that editing acp.toml never needs a restart.
84func (a *App) agentItems() []*ui.MenuItem {
85 var items []*ui.MenuItem
86
87 list, err := a.loadAgents()
88 switch {
89 case err != nil:
90 items = append(items, &ui.MenuItem{Label: "(the agents file has an error)", Enabled: never})
91 case list.Len() == 0:
92 items = append(items, &ui.MenuItem{Label: "(no agents configured)", Enabled: never})
93 default:
94 for _, agent := range list.Agents() {
95 name := agent.Name
96 items = append(items, &ui.MenuItem{Label: name, Action: func() { a.NewAgent(name) }})
97 }
98 }
99
100 return append(items,
101 &ui.MenuItem{Separator: true},
102 &ui.MenuItem{Label: "~C~ancel turn", Shortcut: "Esc", Action: a.CancelAgentTurn, Enabled: a.agentTurnRunning},
103 &ui.MenuItem{Label: "Create agents ~f~ile", Action: a.CreateAgentsFile, Enabled: a.hasNoAgentsFile},
104 &ui.MenuItem{Label: "Agent ~s~tatus", Action: a.AgentStatus},
105 )
106}
107
108// never is the Enabled of a line that is a message rather than a command.
109func never() bool { return false }
110
111func (a *App) runMenu() *ui.Menu {
112 return &ui.Menu{Label: "~R~un", Items: []*ui.MenuItem{
113 {Label: "~C~ompletion", Shortcut: "Ctrl-Space", Action: a.RequestCompletion, Enabled: a.hasWindow},
114 {Separator: true},
115 {Label: "~L~anguage server status", Action: a.ShowLanguageStatus},
116 }}
117}
118
119// codeMenu holds everything the editor asks the language server about the
120// symbol under the cursor.
121//
122// It exists because that work was split across two menus — Describe symbol in
123// Run, Go to definition in Search — and neither is where a reader looks for
124// "what else knows about this name". Both keep their keys: only where they are
125// listed has changed.
126//
127// Every item acts on the **cursor**, not on a selection. Almost every request
128// in the protocol takes a position rather than a range, so asking a user to
129// select something first would be an invented step that answers nothing extra.
130func (a *App) codeMenu() *ui.Menu {
131 return &ui.Menu{Label: "~C~ode", Items: []*ui.MenuItem{
132 {Label: "~D~escribe symbol", Shortcut: "F1", Action: a.DescribeSymbol, Enabled: a.hasWindow},
133 {Separator: true},
134 {Label: "~G~o to definition", Shortcut: "F12", Action: a.GoToDefinition, Enabled: a.hasWindow},
135 {Label: "Go to ~t~ype definition", Action: a.GoToTypeDefinition, Enabled: a.hasWindow},
136 {Label: "Find ~i~mplementations…", Action: a.FindImplementations, Enabled: a.hasWindow},
137 {Label: "Find ~r~eferences…", Shortcut: "Shift-F12", Action: a.FindReferences, Enabled: a.hasWindow},
138 {Separator: true},
139 // Symbol in file has no key on purpose: the obvious one is Ctrl-Shift-O,
140 // and a terminal cannot tell that from Ctrl-O — the shift is lost before
141 // the editor sees it. A menu item that advertises a key which does
142 // nothing is worse than one with no key at all.
143 {Label: "Symbol in ~f~ile…", Action: a.SymbolInFile, Enabled: a.hasWindow},
144 {Label: "Symbol in ~p~roject…", Shortcut: "Ctrl-T", Action: a.SymbolInProject},
145 {Separator: true},
146 {Label: "Pro~b~lems…", Action: a.ShowProblems},
147 }}
148}
149
150func (a *App) optionsMenu() *ui.Menu {
151 return &ui.Menu{Label: "~O~ptions", Items: []*ui.MenuItem{
152 {Label: "~T~heme…", Action: a.ChooseTheme},
153 {Label: "~L~ine numbers", Action: a.ToggleLineNumbers, Enabled: a.hasWindow},
154 {Separator: true},
155 {Label: "~C~reate project settings", Action: a.CreateProjectSettings, Enabled: not(a.HasProjectSettings)},
156 {Label: "~P~roject settings…", Action: a.OpenProjectSettings, Enabled: a.HasProjectSettings},
157 }}
158}
159
160func (a *App) windowMenu() *ui.Menu {
161 return &ui.Menu{Label: "~W~indow", Items: []*ui.MenuItem{
162 {Label: "~N~ext", Shortcut: "F6", Action: a.NextWindow, Enabled: a.hasWindow},
163 {Separator: true},
164 {Label: "New ~t~erminal", Shortcut: "F8", Action: a.NewTerminal},
165 {Label: "~P~roject tree", Shortcut: "F9", Action: a.ProjectTree},
166 {Separator: true},
167 {Label: "~T~ile", Action: a.TileWindows, Enabled: a.hasWindow},
168 {Label: "~C~ascade", Action: a.CascadeWindows, Enabled: a.hasWindow},
169 {Label: "~M~aximise", Action: a.MaximizeWindow, Enabled: a.hasWindow},
170 {Separator: true},
171 {Label: "~L~ist…", Shortcut: "Alt-0", Action: a.ListWindows, Enabled: a.hasWindow},
172 }}
173}
174
175func (a *App) helpMenu() *ui.Menu {
176 return &ui.Menu{Label: "~H~elp", Items: []*ui.MenuItem{
177 {Label: "~K~eyboard", Action: a.ShowKeyboardHelp},
178 {Label: "~A~bout", Action: a.ShowAbout},
179 }}
180}
181
182// statusItems returns the hints along the bottom of the screen. They double as
183// buttons: clicking one runs it.
184func (a *App) statusItems() []ui.StatusItem {
185 return []ui.StatusItem{
186 {Key: "F1", Label: "Describe", Action: a.DescribeSymbol},
187 {Key: "F2", Label: "Save", Action: a.SaveFile},
188 {Key: "F3", Label: "Open", Action: a.OpenFile},
189 {Key: "F6", Label: "Window", Action: a.NextWindow},
190 {Key: "F7", Label: "Next", Action: a.FindNext},
191 {Key: "F10", Label: "Menu", Action: func() { a.menu.OpenMenu(0) }},
192 {Key: "Alt-X", Label: "Exit", Action: a.Quit},
193 }
194}