burterm/internal/tui/decoderview.go
2026-09-14 10:55:07 +03:00

191 lines
5.2 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package tui
import (
"fmt"
"strings"
"github.com/charmbracelet/bubbles/textarea"
"github.com/charmbracelet/bubbles/viewport"
tea "github.com/charmbracelet/bubbletea"
"github.com/r3g1tpr0cs/burterm/internal/decoder"
)
// decoderModel — вкладка Decoder: вход + выход, набор кодировок за
// ctrl-хоткеями (работают независимо от того, в каком поле курсор —
// как и остальные ctrl-комбинации в приложении, они не печатаются как
// текст, поэтому их можно ловить прямо поверх textarea.Update) и тумблер
// направления encode/decode.
type decoderModel struct {
input textarea.Model
output viewport.Model
encode bool // true — кодировать, false — декодировать (для MD5 неприменимо)
lastOp string
errMsg string
focus int // 0 — вход, 1 — выход (скролл)
}
func newDecoderView() decoderModel {
ta := textarea.New()
ta.Placeholder = "Вставь текст для кодирования/декодирования"
ta.Focus()
return decoderModel{
input: ta,
output: viewport.New(0, 0),
encode: true,
}
}
func (m *decoderModel) SetSize(width, height int) {
if width < 0 {
width = 0
}
if height < 0 {
height = 0
}
inHeight := height / 2
m.input.SetWidth(width)
m.input.SetHeight(inHeight)
m.output.Width = width
// 8 фиксированных строк вокруг output-viewport (построчный подсчёт по
// View(): "Вход"-метка 1, разделитель 1, строка статуса 1,
// разделитель+"Выход"-метка 2, разделитель+футер(две строки) 3 — итого
// 8). Раньше здесь стояло -4 — не совпадало с тем, что реально рисует
// View(), причём задолго до того, как футер стал двухстрочным: это
// самостоятельная, отдельная от разбиения футера ошибка в подсчёте.
m.output.Height = height - inHeight - 8
}
func (m decoderModel) Update(msg tea.Msg) (decoderModel, tea.Cmd) {
if key, ok := msg.(tea.KeyMsg); ok {
switch key.String() {
case "tab":
m.focus = (m.focus + 1) % 2
if m.focus == 0 {
m.input.Focus()
} else {
m.input.Blur()
}
return m, nil
case "ctrl+z":
m.encode = !m.encode
return m, nil
case "ctrl+u":
m.apply("URL", func(s string) (string, error) {
if m.encode {
return decoder.URLEncode(s), nil
}
return decoder.URLDecode(s)
})
return m, nil
case "ctrl+b":
m.apply("Base64", func(s string) (string, error) {
if m.encode {
return decoder.Base64Encode(s), nil
}
return decoder.Base64Decode(s)
})
return m, nil
case "ctrl+e":
m.apply("HTML", func(s string) (string, error) {
if m.encode {
return decoder.HTMLEncode(s), nil
}
return decoder.HTMLDecode(s), nil
})
return m, nil
case "ctrl+x":
m.apply("Hex", func(s string) (string, error) {
if m.encode {
return decoder.HexEncode(s), nil
}
return decoder.HexDecode(s)
})
return m, nil
case "ctrl+k":
m.errMsg = ""
m.lastOp = "MD5"
m.output.SetContent(decoder.MD5Hash(m.input.Value()))
return m, nil
case "ctrl+a":
m.autoDecode()
return m, nil
}
}
var cmd tea.Cmd
if m.focus == 0 {
m.input, cmd = m.input.Update(msg)
} else {
m.output, cmd = m.output.Update(msg)
}
return m, cmd
}
func (m *decoderModel) apply(name string, fn func(string) (string, error)) {
out, err := fn(m.input.Value())
if err != nil {
m.errMsg = fmt.Sprintf("%s: %v", name, err)
return
}
m.errMsg = ""
dir := "encode"
if !m.encode {
dir = "decode"
}
m.lastOp = name + " " + dir
m.output.SetContent(out)
}
func (m *decoderModel) autoDecode() {
steps := decoder.SmartDecode(m.input.Value())
if len(steps) == 0 {
m.errMsg = "не удалось распознать ни одной известной кодировки"
return
}
m.errMsg = ""
var b strings.Builder
for i, s := range steps {
b.WriteString(fmt.Sprintf("[%d] %s:\n%s\n\n", i+1, s.Operation, s.Result))
}
m.lastOp = fmt.Sprintf("авто-цепочка (%d шаг(ов))", len(steps))
m.output.SetContent(b.String())
}
func (m decoderModel) View() string {
focusMark := func(i int) string {
if m.focus == i {
return "▸ "
}
return " "
}
dir := "encode"
if !m.encode {
dir = "decode"
}
var b strings.Builder
b.WriteString(focusMark(0) + "Вход\n")
b.WriteString(m.input.View())
b.WriteString("\n\n")
status := fmt.Sprintf("Направление: %s (ctrl+z)", dir)
if m.lastOp != "" {
status += " · последняя операция: " + m.lastOp
}
b.WriteString(status + "\n")
if m.errMsg != "" {
b.WriteString("Ошибка: " + m.errMsg + "\n")
}
b.WriteString("\n" + focusMark(1) + "Выход\n")
b.WriteString(m.output.View())
b.WriteString("\n\nctrl+u URL · ctrl+b Base64 · ctrl+e HTML · ctrl+x Hex · ctrl+k MD5 · ctrl+a авто-цепочка\n")
b.WriteString("f1-f12, pgup — вкладки\n")
return b.String()
}