1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
|
package tui
import (
"fmt"
"time"
tea "charm.land/bubbletea/v2"
)
func (m *Model) normalizeKeyEvent(msg tea.Msg) (tea.Msg, bool) {
switch keyMsg := msg.(type) {
case tea.KeyPressMsg:
keyID := keyEventID(keyMsg)
if m.shouldSuppressPress(keyID) {
return nil, false
}
m.recordKeyEvent(keyMsg, true)
return keyMsg, true
case tea.KeyReleaseMsg:
pressMsg := tea.KeyPressMsg(keyMsg)
keyID := keyEventID(pressMsg)
if m.kb.lastEventWasPress && keyID != "" && keyID == m.kb.lastEventID && time.Since(m.kb.lastEventAt) <= 500*time.Millisecond {
// Some terminals emit both press+release; avoid handling release as a duplicate.
m.kb.lastEventWasPress = false
return nil, false
}
if !releaseHasIdentity(pressMsg) {
// Ignore release messages that don't carry enough identity information.
// Some terminals emit these before a usable press event.
return nil, false
}
// Fallback: treat release as press for terminals that only emit release events.
if shouldSuppressMatchingPressAfterRelease(pressMsg) {
m.armPressSuppression(keyID)
}
m.recordKeyEvent(pressMsg, false)
return pressMsg, true
default:
return msg, true
}
}
func (m *Model) shouldSuppressPress(keyID string) bool {
if m.kb.suppressID == "" {
return false
}
if time.Now().After(m.kb.suppressUntil) {
m.clearPressSuppression()
return false
}
if keyID == "" || keyID != m.kb.suppressID {
return false
}
m.clearPressSuppression()
return true
}
func (m *Model) armPressSuppression(keyID string) {
if keyID == "" {
return
}
// Keep this short so fast repeated key presses still work naturally.
m.kb.suppressID = keyID
m.kb.suppressUntil = time.Now().Add(60 * time.Millisecond)
}
func (m *Model) clearPressSuppression() {
m.kb.suppressID = ""
m.kb.suppressUntil = time.Time{}
}
func (m *Model) recordKeyEvent(msg tea.KeyPressMsg, wasPress bool) {
m.kb.lastEventID = keyEventID(msg)
m.kb.lastEventAt = time.Now()
m.kb.lastEventWasPress = wasPress
}
func keyEventID(msg tea.KeyPressMsg) string {
return fmt.Sprintf("code:%d/mod:%d/key:%q/text:%q", msg.Code, msg.Mod, msg.String(), msg.Text)
}
func releaseHasIdentity(msg tea.KeyPressMsg) bool {
if msg.Text != "" {
return true
}
keyStr := msg.String()
if keyStr != "" && keyStr != "\x00" {
return true
}
// Some terminals emit release-only space events without text identity.
return msg.Code == tea.KeySpace
}
func shouldSuppressMatchingPressAfterRelease(msg tea.KeyPressMsg) bool {
keyStr := msg.String()
return msg.Code == tea.KeySpace || keyStr == " " || keyStr == "space" || msg.Text == " "
}
|