mirror of
https://github.com/jixishi/SerialTerminalForWindowsTerminal.git
synced 2026-06-15 16:42:46 +00:00
e0de872740
Extract ConvertChunk/FormatHexFrame into pkg/charset (zero external deps). Extract UIEvent/UIEventKind/UIPanelKind types into internal/event. Update all references across main package to use qualified imports. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
323 lines
8.3 KiB
Go
323 lines
8.3 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/charmbracelet/bubbles/textinput"
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
"github.com/jixishi/SerialTerminalForWindowsTerminal/internal/event"
|
|
)
|
|
|
|
func (m *uiModel) handleModalKey(msg tea.KeyMsg) bool {
|
|
keyStr := strings.ToLower(msg.String())
|
|
|
|
if m.promptActive {
|
|
return m.handlePromptKey(msg)
|
|
}
|
|
if keyStr == "esc" {
|
|
m.closeModal()
|
|
return true
|
|
}
|
|
if m.panelKind == event.UIPanelNone {
|
|
if keyStr == "enter" {
|
|
m.closeModal()
|
|
}
|
|
return true
|
|
}
|
|
|
|
switch m.panelKind {
|
|
case event.UIPanelForward:
|
|
return m.handleForwardPanelKey(keyStr)
|
|
case event.UIPanelPlugin:
|
|
return m.handlePluginPanelKey(keyStr)
|
|
case event.UIPanelMode:
|
|
return m.handleModePanelKey(keyStr)
|
|
default:
|
|
return true
|
|
}
|
|
}
|
|
|
|
func (m *uiModel) closeModal() {
|
|
m.showModal = false
|
|
m.panelKind = event.UIPanelNone
|
|
m.modalTitle = ""
|
|
m.modalBody = ""
|
|
m.promptActive = false
|
|
m.promptSubmit = nil
|
|
}
|
|
|
|
func (m *uiModel) openPanel(kind event.UIPanelKind) {
|
|
m.showModal = true
|
|
m.panelKind = kind
|
|
m.panelIndex = 0
|
|
m.promptActive = false
|
|
m.promptSubmit = nil
|
|
m.refreshPanel()
|
|
}
|
|
|
|
func (m *uiModel) refreshPanel() {
|
|
switch m.panelKind {
|
|
case event.UIPanelForward:
|
|
m.forwardItems = m.app.forward.List()
|
|
m.panelIndex = clampIndex(m.panelIndex, len(m.forwardItems))
|
|
case event.UIPanelPlugin:
|
|
m.pluginItems = m.app.plugins.List()
|
|
m.panelIndex = clampIndex(m.panelIndex, len(m.pluginItems))
|
|
case event.UIPanelMode:
|
|
m.modeItems = m.buildModeItems()
|
|
m.panelIndex = clampIndex(m.panelIndex, len(m.modeItems))
|
|
}
|
|
}
|
|
|
|
func (m *uiModel) buildModeItems() []modeItem {
|
|
return []modeItem{{"in", "Input Charset", m.app.cfg.inputCode}, {"out", "Output Charset", m.app.cfg.outputCode}, {"end", "Line End", fmt.Sprintf("%q", m.app.cfg.endStr)}, {"frame", "Hex Frame Size", fmt.Sprintf("%d", m.app.cfg.frameSize)}, {"timestamp", "Timestamp", fmt.Sprintf("%v", m.app.cfg.timesTamp)}, {"timefmt", "Timestamp Format", m.app.cfg.timesFmt}}
|
|
}
|
|
|
|
func (m *uiModel) handleForwardPanelKey(key string) bool {
|
|
switch key {
|
|
case "up", "k":
|
|
if m.panelIndex > 0 {
|
|
m.panelIndex--
|
|
}
|
|
return true
|
|
case "down", "j":
|
|
if m.panelIndex < len(m.forwardItems)-1 {
|
|
m.panelIndex++
|
|
}
|
|
return true
|
|
case "r":
|
|
m.refreshPanel()
|
|
return true
|
|
case "a":
|
|
m.startPrompt("Add Forward", "tcp 127.0.0.1:12345", "", func(v string) {
|
|
parts := strings.Fields(v)
|
|
if len(parts) < 2 {
|
|
m.app.Statusf("[forward] usage: <tcp|udp> <address>")
|
|
return
|
|
}
|
|
m.app.handleLine(fmt.Sprintf(".forward add %s %s", parts[0], parts[1]))
|
|
m.refreshPanel()
|
|
})
|
|
return true
|
|
}
|
|
if len(m.forwardItems) == 0 {
|
|
return true
|
|
}
|
|
|
|
sel := m.forwardItems[m.panelIndex]
|
|
switch key {
|
|
case "enter":
|
|
if sel.Enabled {
|
|
m.app.handleLine(fmt.Sprintf(".forward disable %d", sel.ID))
|
|
} else {
|
|
m.app.handleLine(fmt.Sprintf(".forward enable %d", sel.ID))
|
|
}
|
|
m.refreshPanel()
|
|
return true
|
|
case "d", "delete", "backspace":
|
|
m.app.handleLine(fmt.Sprintf(".forward remove %d", sel.ID))
|
|
m.refreshPanel()
|
|
return true
|
|
case "u":
|
|
m.startPrompt("Update Forward #"+fmt.Sprint(sel.ID), "tcp 127.0.0.1:12345", fmt.Sprintf("%s %s", sel.Mode, sel.Address), func(v string) {
|
|
parts := strings.Fields(v)
|
|
if len(parts) < 2 {
|
|
m.app.Statusf("[forward] usage: <tcp|udp> <address>")
|
|
return
|
|
}
|
|
m.app.handleLine(fmt.Sprintf(".forward update %d %s %s", sel.ID, parts[0], parts[1]))
|
|
m.refreshPanel()
|
|
})
|
|
return true
|
|
default:
|
|
return true
|
|
}
|
|
}
|
|
|
|
func (m *uiModel) handlePluginPanelKey(key string) bool {
|
|
switch key {
|
|
case "up", "k":
|
|
if m.panelIndex > 0 {
|
|
m.panelIndex--
|
|
}
|
|
return true
|
|
case "down", "j":
|
|
if m.panelIndex < len(m.pluginItems)-1 {
|
|
m.panelIndex++
|
|
}
|
|
return true
|
|
case "r":
|
|
m.refreshPanel()
|
|
return true
|
|
case "l":
|
|
m.startPrompt("Load Plugin", "./plugins/demo.lua", "", func(v string) {
|
|
path := strings.TrimSpace(v)
|
|
if path == "" {
|
|
m.app.Statusf("[plugin] load path is empty")
|
|
return
|
|
}
|
|
m.app.handleLine(fmt.Sprintf(".plugin load %s", path))
|
|
m.refreshPanel()
|
|
})
|
|
return true
|
|
}
|
|
if len(m.pluginItems) == 0 {
|
|
return true
|
|
}
|
|
|
|
sel := m.pluginItems[m.panelIndex]
|
|
switch key {
|
|
case "enter":
|
|
if sel.Enabled {
|
|
m.app.handleLine(fmt.Sprintf(".plugin disable %s", sel.Name))
|
|
} else {
|
|
m.app.handleLine(fmt.Sprintf(".plugin enable %s", sel.Name))
|
|
}
|
|
m.refreshPanel()
|
|
return true
|
|
case "u":
|
|
m.app.handleLine(fmt.Sprintf(".plugin reload %s", sel.Name))
|
|
m.refreshPanel()
|
|
return true
|
|
case "d", "delete", "backspace":
|
|
m.app.handleLine(fmt.Sprintf(".plugin unload %s", sel.Name))
|
|
m.refreshPanel()
|
|
return true
|
|
default:
|
|
return true
|
|
}
|
|
}
|
|
|
|
func (m *uiModel) handleModePanelKey(key string) bool {
|
|
switch key {
|
|
case "up", "k":
|
|
if m.panelIndex > 0 {
|
|
m.panelIndex--
|
|
}
|
|
return true
|
|
case "down", "j":
|
|
if m.panelIndex < len(m.modeItems)-1 {
|
|
m.panelIndex++
|
|
}
|
|
return true
|
|
case "r":
|
|
m.refreshPanel()
|
|
return true
|
|
}
|
|
if len(m.modeItems) == 0 {
|
|
return true
|
|
}
|
|
|
|
sel := m.modeItems[m.panelIndex]
|
|
switch key {
|
|
case " ":
|
|
if sel.key == "timestamp" {
|
|
if m.app.cfg.timesTamp {
|
|
m.app.handleLine(".mode set timestamp off")
|
|
} else {
|
|
m.app.handleLine(".mode set timestamp on")
|
|
}
|
|
m.refreshPanel()
|
|
}
|
|
return true
|
|
case "enter", "e":
|
|
initial := strings.Trim(sel.value, "\"")
|
|
m.startPrompt("Edit Mode: "+sel.label, "new value", initial, func(v string) {
|
|
m.app.handleLine(fmt.Sprintf(".mode set %s %s", sel.key, v))
|
|
m.refreshPanel()
|
|
})
|
|
return true
|
|
default:
|
|
return true
|
|
}
|
|
}
|
|
|
|
func (m *uiModel) startPrompt(title, hint, initial string, submit func(string)) {
|
|
in := textinput.New()
|
|
in.Prompt = "> "
|
|
in.Placeholder = hint
|
|
in.SetValue(initial)
|
|
in.Focus()
|
|
in.CharLimit = 0
|
|
in.Width = 64
|
|
|
|
m.promptActive = true
|
|
m.promptTitle = title
|
|
m.promptHint = hint
|
|
m.promptInput = in
|
|
m.promptSubmit = submit
|
|
}
|
|
|
|
func (m *uiModel) handlePromptKey(msg tea.KeyMsg) bool {
|
|
key := strings.ToLower(msg.String())
|
|
switch key {
|
|
case "esc":
|
|
m.promptActive = false
|
|
m.promptSubmit = nil
|
|
return true
|
|
case "enter":
|
|
value := strings.TrimSpace(m.promptInput.Value())
|
|
submit := m.promptSubmit
|
|
m.promptActive = false
|
|
m.promptSubmit = nil
|
|
if submit != nil {
|
|
submit(value)
|
|
}
|
|
return true
|
|
default:
|
|
var cmd tea.Cmd
|
|
m.promptInput, cmd = m.promptInput.Update(msg)
|
|
_ = cmd
|
|
return true
|
|
}
|
|
}
|
|
|
|
func (m *uiModel) renderPanel() string {
|
|
switch m.panelKind {
|
|
case event.UIPanelForward:
|
|
return m.renderForwardPanel()
|
|
case event.UIPanelPlugin:
|
|
return m.renderPluginPanel()
|
|
case event.UIPanelMode:
|
|
return m.renderModePanel()
|
|
default:
|
|
return renderModal("Info", "No panel", m.availableModalWidth())
|
|
}
|
|
}
|
|
|
|
func (m *uiModel) renderForwardPanel() string {
|
|
lines := make([]panelLine, 0, len(m.forwardItems)+2)
|
|
if len(m.forwardItems) == 0 {
|
|
lines = append(lines, panelLine{text: "No forwarding targets. Press 'a' to add one."})
|
|
} else {
|
|
lines = append(lines, panelLine{text: "ID Mode Enabled Connected Address InBytes OutBytes"})
|
|
for i, it := range m.forwardItems {
|
|
lines = append(lines, panelLine{text: fmt.Sprintf("%-3d %-5s %-7v %-9v %-22s %-7d %-8d", it.ID, it.Mode, it.Enabled, it.Connected, it.Address, it.ReadBytes, it.WriteByte), selected: i == m.panelIndex})
|
|
}
|
|
}
|
|
return renderPanelModal("Forward Panel", lines, "Up/Down select | Enter toggle enable | a add | u update | d remove | r refresh | Esc close", m.availableModalWidth())
|
|
}
|
|
|
|
func (m *uiModel) renderPluginPanel() string {
|
|
lines := make([]panelLine, 0, len(m.pluginItems)+2)
|
|
if len(m.pluginItems) == 0 {
|
|
lines = append(lines, panelLine{text: "No plugins loaded. Press 'l' to load one."})
|
|
} else {
|
|
lines = append(lines, panelLine{text: "Name Enabled Path"})
|
|
for i, it := range m.pluginItems {
|
|
lines = append(lines, panelLine{text: fmt.Sprintf("%-20s %-7v %s", it.Name, it.Enabled, it.Path), selected: i == m.panelIndex})
|
|
}
|
|
}
|
|
return renderPanelModal("Plugin Panel", lines, "Up/Down select | Enter toggle enable | l load | u reload | d unload | r refresh | Esc close", m.availableModalWidth())
|
|
}
|
|
|
|
func (m *uiModel) renderModePanel() string {
|
|
lines := make([]panelLine, 0, len(m.modeItems)+2)
|
|
lines = append(lines, panelLine{text: "Field Value"})
|
|
for i, it := range m.modeItems {
|
|
lines = append(lines, panelLine{text: fmt.Sprintf("%-16s %s", it.label, it.value), selected: i == m.panelIndex})
|
|
}
|
|
return renderPanelModal("Mode Panel", lines, "Up/Down select | Enter edit value | Space toggle timestamp | r refresh | Esc close", m.availableModalWidth())
|
|
}
|