turbo-editors/turbo-corepublic Fork 0
v1.0.0
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.

snippets.go · 140 lines · 4.7 KBGo Blame HistoryRaw
🛟 Updated. 28d5985 k33g 17h ago1// The Snippets menu: reusable pieces of text, read from a TOML file and
2// inserted at the cursor.
3
4package app
5
6import (
7 "os"
8
📦 Turbo Core f3ade8d k33g 10h ago9 "rickub.com/turbo-editors/turbo-core/snippets"
10 "rickub.com/turbo-editors/turbo-core/syntax"
11 "rickub.com/turbo-editors/turbo-core/ui"
🛟 Updated. 28d5985 k33g 17h ago12)
13
14// createSnippetsLabel is the item that writes a starter file, kept at the
15// bottom of the menu so the groups above it read as the content.
16const createSnippetsLabel = "~C~reate snippets file"
17
18// openSnippetsLabel is its partner, and opens the *project's* file — the one
19// the item above it writes.
20const openSnippetsLabel = "~O~pen snippets file"
21
22// snippetsMenu returns the Snippets menu.
23//
24// Its items are filled in by OnOpen rather than here: they come from a file
25// that can change while the editor runs, and they are filtered by what the
26// front window holds, so there is nothing to decide at start-up.
27//
28// The menu is always on the bar, even when the project has no snippets file at
29// all — otherwise the item that creates one would be unreachable and the
30// feature could not be started.
31func (a *App) snippetsMenu() *ui.Menu {
32 // The hot key is N, not S: Search already owns S, and the bar answers the
33 // first match it finds. TestNoTwoMenusShareAHotKey holds the line.
34 menu := &ui.Menu{Label: "S~n~ippets"}
35 menu.OnOpen = func() { menu.Items = a.snippetItems() }
36 return menu
37}
38
39// snippetItems builds the menu's contents: one submenu per group, then the
40// item that creates a starter file.
41func (a *App) snippetItems() []*ui.MenuItem {
42 items := a.snippetGroupItems()
43 if len(items) > 0 {
44 items = append(items, &ui.MenuItem{Separator: true})
45 }
46 return append(items,
47 &ui.MenuItem{Label: createSnippetsLabel, Action: a.CreateSnippets, Enabled: not(a.HasProjectSnippets)},
48 &ui.MenuItem{Label: openSnippetsLabel, Action: a.OpenSnippets, Enabled: a.HasProjectSnippets},
49 )
50}
51
52// snippetGroupItems turns the snippets that apply to the front window into one
53// submenu per group.
54//
55// A file that cannot be read gives one disabled line saying so, rather than an
56// empty menu: a typo in the file should be visible where the snippets were
57// expected, not silent.
58func (a *App) snippetGroupItems() []*ui.MenuItem {
59 list, err := a.loadSnippets()
60 if err != nil {
61 return []*ui.MenuItem{{Label: "Cannot read snippets", Enabled: func() bool { return false }}}
62 }
63
64 var items []*ui.MenuItem
65 for _, group := range list.Groups(a.snippetLanguage()) {
66 items = append(items, &ui.MenuItem{
67 Label: group.Name,
68 Items: snippetSubmenu(a, group.Snippets),
69 })
70 }
71 return items
72}
73
74// snippetSubmenu turns one group's snippets into the lines of a submenu.
75func snippetSubmenu(a *App, list []snippets.Snippet) []*ui.MenuItem {
76 items := make([]*ui.MenuItem, 0, len(list))
77 for _, snippet := range list {
78 body := snippet.Body // captured per iteration, not read back from the loop
79 items = append(items, &ui.MenuItem{
80 Label: snippet.Name,
81 Action: func() { a.InsertSnippet(body) },
82 Enabled: a.hasWindow,
83 })
84 }
85 return items
86}
87
88// loadSnippets reads the project's snippets and the user's own.
89func (a *App) loadSnippets() (snippets.List, error) {
90 working, err := os.Getwd()
91 if err != nil {
92 return snippets.List{}, err
93 }
94 return snippets.Load(a.profile, working)
95}
96
97// snippetLanguage returns the language the menu should be filtered for: the one
98// the front window holds.
99//
100// A window that is not a file — a terminal, the project tree — and a file whose
101// language is not recognised both give the empty name, which offers every
102// snippet that names no language of its own.
103func (a *App) snippetLanguage() string {
104 view := a.activeView()
105 if view == nil {
106 return ""
107 }
108
109 language := syntax.LanguageOf(view.Buffer().Path(), view.Buffer().Line(0))
110 if language == syntax.LanguageNone {
111 return ""
112 }
113 return language.String()
114}
115
116// InsertSnippet puts a snippet's text into the front window at the cursor.
117//
118// A window that is not a file is left alone: there is nothing to insert into,
119// and the menu items are disabled in that case anyway.
120func (a *App) InsertSnippet(body string) {
121 view := a.activeView()
122 if view == nil {
123 return
124 }
125
126 view.InsertSnippet(body)
127 a.completion.Hide()
128 a.Message("Snippet inserted")
129}
130
131// CreateSnippets writes a starter snippets file for the project and opens it.
132//
133// The file is opened rather than merely written because a snippets file is only
134// useful once it has been read: the examples in it are how the format is
135// learnt. A project that already has one is opened unchanged, which is what
136// someone choosing this twice almost certainly wanted.
137func (a *App) CreateSnippets() {
138 create := func(project string) (string, error) { return snippets.Create(a.profile, project) }
139 a.createProjectFile("Snippets", create, snippets.ErrExists)
140}