@@ -3,6 +3,7 @@ package textinput
33import (
44 "context"
55 "strings"
6+ "sync"
67 "time"
78 "unicode"
89
@@ -14,11 +15,35 @@ import (
1415
1516const defaultBlinkSpeed = time .Millisecond * 530
1617
17- // blinkMsg and blinkCanceled are used to manage cursor blinking.
18- type blinkMsg struct {}
18+ // Internal ID management for text inputs. Necessary for blink integrity when
19+ // multiple text inputs are involved.
20+ var (
21+ lastID int
22+ idMtx sync.Mutex
23+ )
24+
25+ // Return the next ID we should use on the Model.
26+ func nextID () int {
27+ idMtx .Lock ()
28+ defer idMtx .Unlock ()
29+ lastID ++
30+ return lastID
31+ }
32+
33+ // initialBlinkMsg initializes cursor blinking.
34+ type initialBlinkMsg struct {}
35+
36+ // blinkMsg signals that the cursor should blink. It contains metadata that
37+ // allows us to tell if the blink message is the one we're expecting.
38+ type blinkMsg struct {
39+ id int
40+ tag int
41+ }
42+
43+ // blinkCanceled is sent when a blink operation is canceled.
1944type blinkCanceled struct {}
2045
21- // Messages for clipboard events .
46+ // Internal messages for clipboard operations .
2247type pasteMsg string
2348type pasteErrMsg struct { error }
2449
@@ -96,6 +121,12 @@ type Model struct {
96121 // viewport. If 0 or less this setting is ignored.
97122 Width int
98123
124+ // The ID of this Model as it relates to other textinput Models.
125+ id int
126+
127+ // The ID of the blink message we're expecting to receive.
128+ blinkTag int
129+
99130 // Underlying text value.
100131 value []rune
101132
@@ -130,6 +161,7 @@ func NewModel() Model {
130161 CharLimit : 0 ,
131162 PlaceholderStyle : lipgloss .NewStyle ().Foreground (lipgloss .Color ("240" )),
132163
164+ id : nextID (),
133165 value : nil ,
134166 focus : false ,
135167 blink : true ,
@@ -221,7 +253,7 @@ func (m *Model) SetCursorMode(mode CursorMode) tea.Cmd {
221253}
222254
223255// cursorEnd moves the cursor to the end of the input field and returns whether
224- // or not
256+ // the cursor should blink should reset.
225257func (m * Model ) cursorEnd () bool {
226258 return m .setCursor (len (m .value ))
227259}
@@ -258,7 +290,7 @@ func (m *Model) Reset() bool {
258290}
259291
260292// handle a clipboard paste event, if supported. Returns whether or not the
261- // cursor blink should be reset.
293+ // cursor blink should reset.
262294func (m * Model ) handlePaste (v string ) bool {
263295 paste := []rune (v )
264296
@@ -599,7 +631,30 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
599631 }
600632 }
601633
634+ case initialBlinkMsg :
635+ // We accept all initialBlinkMsgs genrated by the Blink command.
636+
637+ if m .cursorMode != CursorBlink || ! m .focus {
638+ return m , nil
639+ }
640+
641+ cmd := m .blinkCmd ()
642+ return m , cmd
643+
602644 case blinkMsg :
645+ // We're choosy about whether to accept blinkMsgs so that our cursor
646+ // only exactly when it should.
647+
648+ // Is this model blinkable?
649+ if m .cursorMode != CursorBlink || ! m .focus {
650+ return m , nil
651+ }
652+
653+ // Were we expecting this blink message?
654+ if msg .id != m .id || msg .tag != m .blinkTag {
655+ return m , nil
656+ }
657+
603658 var cmd tea.Cmd
604659 if m .cursorMode == CursorBlink {
605660 m .blink = ! m .blink
@@ -690,27 +745,33 @@ func (m Model) cursorView(v string) string {
690745}
691746
692747// blinkCmd is an internal command used to manage cursor blinking.
693- func (m Model ) blinkCmd () tea.Cmd {
748+ func (m * Model ) blinkCmd () tea.Cmd {
749+ if m .cursorMode != CursorBlink {
750+ return nil
751+ }
752+
694753 if m .blinkCtx != nil && m .blinkCtx .cancel != nil {
695754 m .blinkCtx .cancel ()
696755 }
697756
698757 ctx , cancel := context .WithTimeout (m .blinkCtx .ctx , m .BlinkSpeed )
699758 m .blinkCtx .cancel = cancel
700759
760+ m .blinkTag ++
761+
701762 return func () tea.Msg {
702763 defer cancel ()
703764 <- ctx .Done ()
704765 if ctx .Err () == context .DeadlineExceeded {
705- return blinkMsg {}
766+ return blinkMsg {id : m . id , tag : m . blinkTag }
706767 }
707768 return blinkCanceled {}
708769 }
709770}
710771
711772// Blink is a command used to initialize cursor blinking.
712773func Blink () tea.Msg {
713- return blinkMsg {}
774+ return initialBlinkMsg {}
714775}
715776
716777// Paste is a command for pasting from the clipboard into the text input.
0 commit comments