Fix: universal hotkeys using Ctrl+combinations
- All hotkeys now use Ctrl+letter (Ctrl+A, Ctrl+E, Ctrl+D, Ctrl+T, Ctrl+F, Ctrl+Q) - Works on ANY keyboard layout (EN, FR, DE, RU, etc.) - Ctrl+letter generates same control character regardless of layout - Updated help text and README Rationale: Ctrl+A always produces byte 0x01 (SOH) regardless of whether the physical key is QWERTY 'a', AZERTY 'a', or ЙЦУКЕН 'ф'. This is the standard approach used by vim, tmux, ranger, etc.
This commit is contained in:
parent
3b509d5d2e
commit
d1bb216d82
14
README.md
14
README.md
|
|
@ -82,17 +82,17 @@ sshkeeper ssh-config install-include
|
||||||
sshkeeper
|
sshkeeper
|
||||||
```
|
```
|
||||||
|
|
||||||
Клавиши (раскладка не важна, работает и на русской):
|
Клавиши (работают на любой раскладке — используются Ctrl+комбинации):
|
||||||
|
|
||||||
| Клавиша | Действие |
|
| Клавиша | Действие |
|
||||||
|---------|----------|
|
|---------|----------|
|
||||||
| Enter | Подключиться к серверу |
|
| Enter | Подключиться к серверу |
|
||||||
| q / й | Выход |
|
| Ctrl+A | Добавить сервер |
|
||||||
| a / ф | Добавить сервер |
|
| Ctrl+E | Редактировать сервер |
|
||||||
| e / у | Редактировать сервер |
|
| Ctrl+D | Удалить сервер |
|
||||||
| d / в | Удалить сервер |
|
| Ctrl+T | Проверить подключение |
|
||||||
| t / е | Проверить подключение |
|
| Ctrl+F | Поиск |
|
||||||
| / | Поиск |
|
| Ctrl+Q / Ctrl+C | Выход |
|
||||||
|
|
||||||
В форме добавления/редактирования:
|
В форме добавления/редактирования:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -222,35 +222,35 @@ func (m *tuiModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
return m, nil
|
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) {
|
func (m *tuiModel) updateList(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||||
// Check key by runes (layout-independent)
|
switch msg.Type {
|
||||||
if msg.Type == tea.KeyRunes {
|
case tea.KeyEnter:
|
||||||
switch {
|
// Connect to selected server
|
||||||
case matchKeys(msg, "q", "й"):
|
if item, ok := m.list.SelectedItem().(serverItem); ok {
|
||||||
|
return m, func() tea.Msg {
|
||||||
|
return connectRequestMsg{server: item.server}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case tea.KeyCtrlC, tea.KeyCtrlQ:
|
||||||
return m, tea.Quit
|
return m, tea.Quit
|
||||||
case matchKeys(msg, "/", "?"):
|
|
||||||
m.screen = screenSearch
|
case tea.KeyCtrlA:
|
||||||
m.searchInput.Focus()
|
// Add server
|
||||||
return m, nil
|
|
||||||
case matchKeys(msg, "a", "ф"):
|
|
||||||
m.form = newFormModel(m.width, m.height)
|
m.form = newFormModel(m.width, m.height)
|
||||||
m.screen = screenForm
|
m.screen = screenForm
|
||||||
return m, nil
|
return m, nil
|
||||||
case matchKeys(msg, "e", "у"):
|
|
||||||
|
case tea.KeyCtrlE:
|
||||||
|
// Edit selected server
|
||||||
if item, ok := m.list.SelectedItem().(serverItem); ok {
|
if item, ok := m.list.SelectedItem().(serverItem); ok {
|
||||||
m.form = newEditFormModel(item.server, m.width, m.height)
|
m.form = newEditFormModel(item.server, m.width, m.height)
|
||||||
m.screen = screenForm
|
m.screen = screenForm
|
||||||
}
|
}
|
||||||
return m, nil
|
return m, nil
|
||||||
case matchKeys(msg, "d", "в"):
|
|
||||||
|
case tea.KeyCtrlD:
|
||||||
|
// Delete selected server
|
||||||
if item, ok := m.list.SelectedItem().(serverItem); ok {
|
if item, ok := m.list.SelectedItem().(serverItem); ok {
|
||||||
return m, func() tea.Msg {
|
return m, func() tea.Msg {
|
||||||
err := DeleteServer(item.server.Alias)
|
err := DeleteServer(item.server.Alias)
|
||||||
|
|
@ -261,25 +261,22 @@ func (m *tuiModel) updateList(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||||
return serversLoadedMsg{servers: servers, err: err}
|
return serversLoadedMsg{servers: servers, err: err}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case matchKeys(msg, "t", "е"):
|
|
||||||
|
case tea.KeyCtrlT:
|
||||||
|
// Test connection
|
||||||
if item, ok := m.list.SelectedItem().(serverItem); ok {
|
if item, ok := m.list.SelectedItem().(serverItem); ok {
|
||||||
return m, func() tea.Msg {
|
return m, func() tea.Msg {
|
||||||
ok, testErr := TestConnection(item.server)
|
ok, testErr := TestConnection(item.server)
|
||||||
return testDoneMsg{ok: ok, err: testErr}
|
return testDoneMsg{ok: ok, err: testErr}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch msg.Type {
|
case tea.KeyCtrlF, tea.KeyCtrlS:
|
||||||
case tea.KeyEnter:
|
// Search
|
||||||
if item, ok := m.list.SelectedItem().(serverItem); ok {
|
m.screen = screenSearch
|
||||||
return m, func() tea.Msg {
|
m.searchInput.Focus()
|
||||||
return connectRequestMsg{server: item.server}
|
return m, nil
|
||||||
}
|
|
||||||
}
|
|
||||||
case tea.KeyCtrlC:
|
|
||||||
return m, tea.Quit
|
|
||||||
default:
|
default:
|
||||||
var cmd tea.Cmd
|
var cmd tea.Cmd
|
||||||
m.list, cmd = m.list.Update(msg)
|
m.list, cmd = m.list.Update(msg)
|
||||||
|
|
@ -342,11 +339,11 @@ func (m *tuiModel) View() string {
|
||||||
case screenList:
|
case screenList:
|
||||||
b.WriteString(m.list.View())
|
b.WriteString(m.list.View())
|
||||||
b.WriteString("\n")
|
b.WriteString("\n")
|
||||||
b.WriteString(helpStyle.Render("Enter connect | a add | e edit | d delete | t test | / search | q quit"))
|
b.WriteString(helpStyle.Render("Enter connect | Ctrl+A add | Ctrl+E edit | Ctrl+D del | Ctrl+T test | Ctrl+F search | Ctrl+Q quit"))
|
||||||
|
|
||||||
case screenSearch:
|
case screenSearch:
|
||||||
b.WriteString("Search: " + m.searchInput.View() + "\n")
|
b.WriteString("Search: " + m.searchInput.View() + "\n")
|
||||||
b.WriteString(helpStyle.Render("Enter search | Esc cancel"))
|
b.WriteString(helpStyle.Render("Type to search | Enter confirm | Esc cancel"))
|
||||||
|
|
||||||
case screenForm:
|
case screenForm:
|
||||||
b.WriteString(m.form.View())
|
b.WriteString(m.form.View())
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue