1package plugin
2
3import (
4 "log"
5
6 "charm.land/lipgloss/v2"
7 lua "github.com/yuin/gopher-lua"
8)
9
10// registerAPI registers the "matcha" module into the Lua VM.
11func (m *Manager) registerAPI() {
12 L := m.state
13
14 mod := L.RegisterModule("matcha", map[string]lua.LGFunction{
15 "on": m.luaOn,
16 "log": m.luaLog,
17 "notify": m.luaNotify,
18 "set_status": m.luaSetStatus,
19 "set_compose_field": m.luaSetComposeField,
20 "bind_key": m.luaBindKey,
21 "http": m.luaHTTP,
22 "prompt": m.luaPrompt,
23 "style": m.luaStyle,
24 })
25
26 L.SetField(mod, "_VERSION", lua.LString("0.1.0"))
27}
28
29// matcha.on(event, callback) — register a hook callback.
30func (m *Manager) luaOn(L *lua.LState) int {
31 event := L.CheckString(1)
32 fn := L.CheckFunction(2)
33 m.registerHook(event, fn)
34 return 0
35}
36
37// matcha.log(msg) — log a message to stderr.
38func (m *Manager) luaLog(L *lua.LState) int {
39 msg := L.CheckString(1)
40 log.Printf("[plugin] %s", msg)
41 return 0
42}
43
44// matcha.set_status(area, text) — set a persistent status string for a view area.
45// Valid areas: "inbox", "composer", "email_view".
46func (m *Manager) luaSetStatus(L *lua.LState) int {
47 area := L.CheckString(1)
48 text := L.CheckString(2)
49 m.statuses[area] = text
50 return 0
51}
52
53// matcha.notify(msg [, seconds]) — show a temporary notification in the TUI.
54// The optional second argument sets the display duration in seconds (default 2).
55func (m *Manager) luaNotify(L *lua.LState) int {
56 m.pendingNotification = L.CheckString(1)
57 m.pendingDuration = float64(L.OptNumber(2, 2))
58 return 0
59}
60
61// matcha.bind_key(key, area, description, callback) — register a custom keyboard shortcut.
62// Valid areas: "inbox", "email_view", "composer".
63func (m *Manager) luaBindKey(L *lua.LState) int {
64 key := L.CheckString(1)
65 area := L.CheckString(2)
66 description := L.CheckString(3)
67 fn := L.CheckFunction(4)
68
69 switch area {
70 case "inbox", "email_view", "composer":
71 m.bindings = append(m.bindings, KeyBinding{
72 Key: key,
73 Area: area,
74 Description: description,
75 Fn: fn,
76 })
77 default:
78 L.ArgError(2, "invalid area: must be \"inbox\", \"email_view\", or \"composer\"")
79 }
80 return 0
81}
82
83// matcha.style(text, opts) — wrap text in lipgloss styling and return the
84// resulting ANSI-styled string. opts is a table with optional keys:
85// - color, bg: string (hex "#rrggbb", ANSI 256 number as string, or named like "red")
86// - bold, italic, underline, strikethrough, faint, blink, reverse: bool
87//
88// Plugins use this from email_body_render callbacks to style matched substrings:
89//
90// matcha.on("email_body_render", function(email, body)
91// return (body:gsub("TODO", function(m)
92// return matcha.style(m, {color = "#ff0000", bold = true})
93// end))
94// end)
95func (m *Manager) luaStyle(L *lua.LState) int {
96 text := L.CheckString(1)
97 opts := L.OptTable(2, nil)
98
99 style := lipgloss.NewStyle()
100 if opts != nil {
101 if v, ok := opts.RawGetString("color").(lua.LString); ok && v != "" {
102 style = style.Foreground(lipgloss.Color(string(v)))
103 }
104 if v, ok := opts.RawGetString("bg").(lua.LString); ok && v != "" {
105 style = style.Background(lipgloss.Color(string(v)))
106 }
107 if lua.LVAsBool(opts.RawGetString("bold")) {
108 style = style.Bold(true)
109 }
110 if lua.LVAsBool(opts.RawGetString("italic")) {
111 style = style.Italic(true)
112 }
113 if lua.LVAsBool(opts.RawGetString("underline")) {
114 style = style.Underline(true)
115 }
116 if lua.LVAsBool(opts.RawGetString("strikethrough")) {
117 style = style.Strikethrough(true)
118 }
119 if lua.LVAsBool(opts.RawGetString("faint")) {
120 style = style.Faint(true)
121 }
122 if lua.LVAsBool(opts.RawGetString("blink")) {
123 style = style.Blink(true)
124 }
125 if lua.LVAsBool(opts.RawGetString("reverse")) {
126 style = style.Reverse(true)
127 }
128 }
129
130 L.Push(lua.LString(style.Render(text)))
131 return 1
132}
133
134// matcha.set_compose_field(field, value) — set a compose field value.
135// Valid fields: "to", "cc", "bcc", "subject", "body".
136func (m *Manager) luaSetComposeField(L *lua.LState) int {
137 field := L.CheckString(1)
138 value := L.CheckString(2)
139
140 switch field {
141 case "to", "cc", "bcc", "subject", "body":
142 m.pendingFields[field] = value
143 default:
144 L.ArgError(1, "invalid field: must be \"to\", \"cc\", \"bcc\", \"subject\", or \"body\"")
145 }
146 return 0
147}