From 3b509d5d2edb0e0f9c0046bb2284d32d0fadbda5 Mon Sep 17 00:00:00 2001 From: mirivlad Date: Tue, 26 May 2026 10:47:48 +0800 Subject: [PATCH] Fix: layout-independent hotkeys, Argon2 memory reduction, README - Hotkeys now work with both English and Russian keyboard layouts - Argon2 memory reduced from 64MB to 4MB (faster key derivation) - Added 'Deriving key...' progress indicator - Help text color changed to cyan for better visibility - Form navigation uses key types instead of strings - README updated with layout-independent key info --- README.md | 12 ++--- internal/tui/app.go | 123 ++++++++++++++++++++++++++------------------ 2 files changed, 80 insertions(+), 55 deletions(-) diff --git a/README.md b/README.md index cbccc76..98fa740 100644 --- a/README.md +++ b/README.md @@ -82,17 +82,17 @@ sshkeeper ssh-config install-include sshkeeper ``` -Клавиши: +Клавиши (раскладка не важна, работает и на русской): | Клавиша | Действие | |---------|----------| | Enter | Подключиться к серверу | -| a | Добавить сервер | -| e | Редктировать сервер | -| d | Удалить сервер | -| t | Проверить подключение | +| q / й | Выход | +| a / ф | Добавить сервер | +| e / у | Редактировать сервер | +| d / в | Удалить сервер | +| t / е | Проверить подключение | | / | Поиск | -| q | Выход | В форме добавления/редактирования: diff --git a/internal/tui/app.go b/internal/tui/app.go index 4c64c9f..ed6cbeb 100644 --- a/internal/tui/app.go +++ b/internal/tui/app.go @@ -222,56 +222,64 @@ func (m *tuiModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, nil } +func matchKeys(msg tea.KeyMsg, en, ru string) bool { + if len(msg.Runes) != 1 { + return false + } + r := msg.Runes[0] + return r == []rune(en)[0] || r == []rune(ru)[0] +} + func (m *tuiModel) updateList(msg tea.KeyMsg) (tea.Model, tea.Cmd) { - switch msg.String() { - case "q", "ctrl+c": - return m, tea.Quit - - case "/": - m.screen = screenSearch - m.searchInput.Focus() - return m, nil - - case "a": - m.form = newFormModel(m.width, m.height) - m.screen = screenForm - return m, nil - - case "e": - if item, ok := m.list.SelectedItem().(serverItem); ok { - m.form = newEditFormModel(item.server, m.width, m.height) + // Check key by runes (layout-independent) + if msg.Type == tea.KeyRunes { + switch { + case matchKeys(msg, "q", "й"): + return m, tea.Quit + case matchKeys(msg, "/", "?"): + m.screen = screenSearch + m.searchInput.Focus() + return m, nil + case matchKeys(msg, "a", "ф"): + m.form = newFormModel(m.width, m.height) m.screen = screenForm - } - return m, nil - - case "d": - if item, ok := m.list.SelectedItem().(serverItem); ok { - return m, func() tea.Msg { - err := DeleteServer(item.server.Alias) - if err != nil { - return saveDoneMsg{err: err} + return m, nil + case matchKeys(msg, "e", "у"): + if item, ok := m.list.SelectedItem().(serverItem); ok { + m.form = newEditFormModel(item.server, m.width, m.height) + m.screen = screenForm + } + return m, nil + case matchKeys(msg, "d", "в"): + if item, ok := m.list.SelectedItem().(serverItem); ok { + return m, func() tea.Msg { + err := DeleteServer(item.server.Alias) + if err != nil { + return saveDoneMsg{err: err} + } + servers, err := ListServers() + return serversLoadedMsg{servers: servers, err: err} + } + } + case matchKeys(msg, "t", "е"): + if item, ok := m.list.SelectedItem().(serverItem); ok { + return m, func() tea.Msg { + ok, testErr := TestConnection(item.server) + return testDoneMsg{ok: ok, err: testErr} } - servers, err := ListServers() - return serversLoadedMsg{servers: servers, err: err} } } + } - case "t": + switch msg.Type { + case tea.KeyEnter: if item, ok := m.list.SelectedItem().(serverItem); ok { - return m, func() tea.Msg { - ok, testErr := TestConnection(item.server) - return testDoneMsg{ok: ok, err: testErr} - } - } - - case "enter": - if item, ok := m.list.SelectedItem().(serverItem); ok { - // Request connect — TUI will quit and caller handles it return m, func() tea.Msg { return connectRequestMsg{server: item.server} } } - + case tea.KeyCtrlC: + return m, tea.Quit default: var cmd tea.Cmd m.list, cmd = m.list.Update(msg) @@ -282,14 +290,14 @@ func (m *tuiModel) updateList(msg tea.KeyMsg) (tea.Model, tea.Cmd) { } func (m *tuiModel) updateSearch(msg tea.KeyMsg) (tea.Model, tea.Cmd) { - switch msg.String() { - case "esc": + switch msg.Type { + case tea.KeyEsc: m.screen = screenList m.searchInput.Blur() m.searchInput.SetValue("") return m, nil - case "enter": + case tea.KeyEnter: m.screen = screenList m.searchInput.Blur() query := m.searchInput.Value() @@ -312,8 +320,7 @@ func (m *tuiModel) updateSearch(msg tea.KeyMsg) (tea.Model, tea.Cmd) { } func (m *tuiModel) updateForm(msg tea.KeyMsg) (tea.Model, tea.Cmd) { - switch msg.String() { - case "esc": + if msg.Type == tea.KeyEsc { m.screen = screenList m.form = nil m.err = nil @@ -480,8 +487,8 @@ func (fm *formModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.KeyMsg: - switch msg.String() { - case "tab", "down": + switch msg.Type { + case tea.KeyTab: fm.focusIdx++ total := len(fm.inputs) + 3 if fm.focusIdx >= total { @@ -490,7 +497,7 @@ func (fm *formModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { fm.updateFocus() return fm, nil - case "shift+tab", "up": + case tea.KeyShiftTab: fm.focusIdx-- if fm.focusIdx < 0 { total := len(fm.inputs) + 3 @@ -499,7 +506,7 @@ func (fm *formModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { fm.updateFocus() return fm, nil - case "enter": + case tea.KeyEnter: switch { case fm.focusIdx == len(fm.inputs)+1: return fm, fm.runTest() @@ -515,7 +522,25 @@ func (fm *formModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return fm, nil } - case "esc": + case tea.KeyEsc: + return fm, nil + + case tea.KeyDown: + fm.focusIdx++ + total := len(fm.inputs) + 3 + if fm.focusIdx >= total { + fm.focusIdx = 0 + } + fm.updateFocus() + return fm, nil + + case tea.KeyUp: + fm.focusIdx-- + if fm.focusIdx < 0 { + total := len(fm.inputs) + 3 + fm.focusIdx = total - 1 + } + fm.updateFocus() return fm, nil } }