mirror of
https://github.com/anotherhadi/spilltea.git
synced 2026-05-20 09:42:34 +02:00
Init
Signed-off-by: Hadi <112569860+anotherhadi@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
package scope
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"charm.land/bubbles/v2/help"
|
||||
"charm.land/bubbles/v2/key"
|
||||
"charm.land/bubbles/v2/textarea"
|
||||
tea "charm.land/bubbletea/v2"
|
||||
"charm.land/lipgloss/v2"
|
||||
"github.com/anotherhadi/spilltea/internal/keys"
|
||||
"github.com/anotherhadi/spilltea/internal/style"
|
||||
)
|
||||
|
||||
const (
|
||||
fieldNone = -1
|
||||
fieldWhitelist = 0
|
||||
fieldBlacklist = 1
|
||||
)
|
||||
|
||||
const (
|
||||
minTaH = 3
|
||||
maxTaH = 12
|
||||
fixedH = 8 // (blank + label + desc + blank) x2
|
||||
)
|
||||
|
||||
type ScopeChangedMsg struct {
|
||||
Whitelist []string
|
||||
Blacklist []string
|
||||
}
|
||||
|
||||
type Model struct {
|
||||
focusIdx int
|
||||
|
||||
wlTextarea textarea.Model
|
||||
blTextarea textarea.Model
|
||||
|
||||
innerH int
|
||||
width int
|
||||
height int
|
||||
|
||||
help help.Model
|
||||
}
|
||||
|
||||
func New(name, path string) Model {
|
||||
wl := style.NewTextarea(true)
|
||||
wl.Placeholder = "one pattern per line..."
|
||||
|
||||
bl := style.NewTextarea(true)
|
||||
bl.Placeholder = "one pattern per line..."
|
||||
bl.Blur()
|
||||
|
||||
return Model{
|
||||
focusIdx: fieldNone,
|
||||
wlTextarea: wl,
|
||||
blTextarea: bl,
|
||||
help: style.NewHelp(),
|
||||
}
|
||||
}
|
||||
|
||||
func (m Model) Init() tea.Cmd { return nil }
|
||||
|
||||
func (m *Model) SetScope(whitelist, blacklist []string) {
|
||||
m.wlTextarea.SetValue(strings.Join(whitelist, "\n"))
|
||||
m.blTextarea.SetValue(strings.Join(blacklist, "\n"))
|
||||
}
|
||||
|
||||
func (m *Model) SetSize(w, h int) {
|
||||
m.width = w
|
||||
m.height = h
|
||||
m.syncLayout()
|
||||
}
|
||||
|
||||
func (m *Model) syncLayout() {
|
||||
if m.width == 0 {
|
||||
return
|
||||
}
|
||||
m.help.SetWidth(m.width - 2)
|
||||
|
||||
statusH := strings.Count(m.renderStatusBar(), "\n") + 1
|
||||
panelH := m.height - statusH
|
||||
m.innerH = max(1, style.PanelContentH(panelH))
|
||||
|
||||
taH := (m.innerH - fixedH) / 2
|
||||
if taH < minTaH {
|
||||
taH = minTaH
|
||||
}
|
||||
if taH > maxTaH {
|
||||
taH = maxTaH
|
||||
}
|
||||
// width - 2 (panel border) - 1 (leading space in view) - 3 (right margin + cursor)
|
||||
taW := max(1, m.width-6)
|
||||
m.wlTextarea.SetWidth(taW)
|
||||
m.wlTextarea.SetHeight(taH)
|
||||
m.blTextarea.SetWidth(taW)
|
||||
m.blTextarea.SetHeight(taH)
|
||||
}
|
||||
|
||||
func (m Model) IsEditing() bool {
|
||||
return m.focusIdx == fieldWhitelist || m.focusIdx == fieldBlacklist
|
||||
}
|
||||
|
||||
func (m *Model) scopeChangedCmd() tea.Cmd {
|
||||
wl := parseLines(m.wlTextarea.Value())
|
||||
bl := parseLines(m.blTextarea.Value())
|
||||
return func() tea.Msg {
|
||||
return ScopeChangedMsg{Whitelist: wl, Blacklist: bl}
|
||||
}
|
||||
}
|
||||
|
||||
func parseLines(s string) []string {
|
||||
var out []string
|
||||
for _, line := range strings.Split(s, "\n") {
|
||||
if t := strings.TrimSpace(line); t != "" {
|
||||
out = append(out, t)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (m Model) renderStatusBar() string {
|
||||
return lipgloss.NewStyle().Padding(0, 1).Render(
|
||||
m.help.View(formKeyMap{focusIdx: m.focusIdx}),
|
||||
)
|
||||
}
|
||||
|
||||
type formKeyMap struct {
|
||||
focusIdx int
|
||||
}
|
||||
|
||||
func (k formKeyMap) ShortHelp() []key.Binding {
|
||||
cycle := keys.Keys.Global.CycleFocus
|
||||
hlp := keys.Keys.Global.Help
|
||||
|
||||
switch k.focusIdx {
|
||||
case fieldWhitelist, fieldBlacklist:
|
||||
esc := keys.Keys.Global.Escape
|
||||
escBinding := key.NewBinding(key.WithKeys(esc.Keys()...), key.WithHelp(esc.Help().Key, "unfocus"))
|
||||
return []key.Binding{
|
||||
key.NewBinding(key.WithKeys("enter"), key.WithHelp("enter", "new line")),
|
||||
escBinding,
|
||||
cycle,
|
||||
}
|
||||
}
|
||||
return []key.Binding{cycle, hlp}
|
||||
}
|
||||
|
||||
func (k formKeyMap) FullHelp() [][]key.Binding {
|
||||
return [][]key.Binding{k.ShortHelp()}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package scope
|
||||
|
||||
import (
|
||||
"charm.land/bubbles/v2/key"
|
||||
tea "charm.land/bubbletea/v2"
|
||||
"github.com/anotherhadi/spilltea/internal/keys"
|
||||
)
|
||||
|
||||
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
kp, isKey := msg.(tea.KeyPressMsg)
|
||||
if !isKey {
|
||||
return m, nil
|
||||
}
|
||||
|
||||
if key.Matches(kp, keys.Keys.Global.CycleFocus) {
|
||||
return m.cycleFocus()
|
||||
}
|
||||
|
||||
if key.Matches(kp, keys.Keys.Global.Help) && !m.IsEditing() {
|
||||
m.help.ShowAll = !m.help.ShowAll
|
||||
return m, nil
|
||||
}
|
||||
|
||||
switch m.focusIdx {
|
||||
case fieldWhitelist:
|
||||
if key.Matches(kp, keys.Keys.Global.Escape) {
|
||||
return m.blurAll()
|
||||
}
|
||||
var cmd tea.Cmd
|
||||
m.wlTextarea, cmd = m.wlTextarea.Update(kp)
|
||||
return m, cmd
|
||||
|
||||
case fieldBlacklist:
|
||||
if key.Matches(kp, keys.Keys.Global.Escape) {
|
||||
return m.blurAll()
|
||||
}
|
||||
var cmd tea.Cmd
|
||||
m.blTextarea, cmd = m.blTextarea.Update(kp)
|
||||
return m, cmd
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m Model) blurAll() (tea.Model, tea.Cmd) {
|
||||
m.wlTextarea.Blur()
|
||||
m.blTextarea.Blur()
|
||||
m.focusIdx = fieldNone
|
||||
m.syncLayout()
|
||||
return m, m.scopeChangedCmd()
|
||||
}
|
||||
|
||||
func (m Model) cycleFocus() (tea.Model, tea.Cmd) {
|
||||
scopeCmd := m.scopeChangedCmd()
|
||||
|
||||
var focusCmd tea.Cmd
|
||||
switch m.focusIdx {
|
||||
case fieldNone, fieldBlacklist:
|
||||
m.blTextarea.Blur()
|
||||
m.focusIdx = fieldWhitelist
|
||||
focusCmd = m.wlTextarea.Focus()
|
||||
case fieldWhitelist:
|
||||
m.wlTextarea.Blur()
|
||||
m.focusIdx = fieldBlacklist
|
||||
focusCmd = m.blTextarea.Focus()
|
||||
}
|
||||
|
||||
m.syncLayout()
|
||||
return m, tea.Batch(focusCmd, scopeCmd)
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package scope
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
tea "charm.land/bubbletea/v2"
|
||||
"charm.land/lipgloss/v2"
|
||||
"github.com/anotherhadi/spilltea/internal/icons"
|
||||
"github.com/anotherhadi/spilltea/internal/style"
|
||||
)
|
||||
|
||||
func (m Model) View() tea.View {
|
||||
if m.width == 0 {
|
||||
return tea.NewView("")
|
||||
}
|
||||
|
||||
s := style.S
|
||||
|
||||
statusBar := m.renderStatusBar()
|
||||
statusH := strings.Count(statusBar, "\n") + 1
|
||||
panelH := m.height - statusH
|
||||
innerH := max(1, style.PanelContentH(panelH))
|
||||
|
||||
taH := (innerH - fixedH) / 2
|
||||
if taH < minTaH {
|
||||
taH = minTaH
|
||||
}
|
||||
if taH > maxTaH {
|
||||
taH = maxTaH
|
||||
}
|
||||
|
||||
var lines []string
|
||||
add := func(l string) { lines = append(lines, l) }
|
||||
|
||||
add("")
|
||||
add(fieldLabel("Whitelist", m.focusIdx == fieldWhitelist))
|
||||
add(" " + s.Faint.Render("If non-empty, only matching requests are intercepted."))
|
||||
add("")
|
||||
wlContentLines := strings.Count(m.wlTextarea.Value(), "\n") + 1
|
||||
for _, l := range taLines(m.wlTextarea.View(), taH, wlContentLines) {
|
||||
add(" " + l)
|
||||
}
|
||||
|
||||
add("")
|
||||
add(fieldLabel("Blacklist", m.focusIdx == fieldBlacklist))
|
||||
add(" " + s.Faint.Render("Matching requests are always excluded from history."))
|
||||
add("")
|
||||
blContentLines := strings.Count(m.blTextarea.Value(), "\n") + 1
|
||||
for _, l := range taLines(m.blTextarea.View(), taH, blContentLines) {
|
||||
add(" " + l)
|
||||
}
|
||||
|
||||
for len(lines) < innerH {
|
||||
lines = append(lines, "")
|
||||
}
|
||||
content := strings.Join(lines[:innerH], "\n")
|
||||
|
||||
panel := style.RenderWithTitle(s.PanelFocused, icons.I.Scope+"Scopes", content, m.width, panelH)
|
||||
return tea.NewView(lipgloss.JoinVertical(lipgloss.Left, panel, statusBar))
|
||||
}
|
||||
|
||||
func fieldLabel(name string, focused bool) string {
|
||||
s := style.S
|
||||
c := s.MutedFg
|
||||
if focused {
|
||||
c = s.Primary
|
||||
}
|
||||
return " " + lipgloss.NewStyle().Foreground(c).Bold(focused).Render(name)
|
||||
}
|
||||
|
||||
func taLines(view string, h int, contentLines int) []string {
|
||||
raw := strings.Split(strings.TrimRight(view, "\n"), "\n")
|
||||
tilde := style.S.Faint.Render("~")
|
||||
for len(raw) < h {
|
||||
raw = append(raw, tilde)
|
||||
}
|
||||
if len(raw) > h {
|
||||
raw = raw[:h]
|
||||
}
|
||||
for i := contentLines; i < len(raw); i++ {
|
||||
raw[i] = tilde
|
||||
}
|
||||
return raw
|
||||
}
|
||||
Reference in New Issue
Block a user