-
-
Notifications
You must be signed in to change notification settings - Fork 344
feature: add negotiation step for terminal attributes #909
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
WalkthroughParses CSI primary device attribute (DA) sequences prefixed with '?' into an internal unexported Changes
Sequence Diagram(s)sequenceDiagram
participant Term as Terminal (TTY)
participant Parser as CSI Parser (input.go)
participant EventBus as Event Router / postEvent
participant Screen as tScreen (main)
participant Init as initQ / processInitQ
Term->>Parser: emit CSI sequence starting with "?"
Parser->>Parser: detect hasQM, parse params -> build eventPrimaryAttributes
Parser->>EventBus: post eventPrimaryAttributes
EventBus->>Screen: filterEvents() routes eventPrimaryAttributes -> initQ
Screen->>Init: enqueue into initQ
Init->>Init: processInitQ() (1s window) evaluate attributes -> set noColor/setClipboard, mark initted
Init->>Screen: signal initialization complete
Note right of Screen: other events continue to main eventQ normally
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes
Possibly related PRs
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: defaults Review profile: CHILL Plan: Pro 📒 Files selected for processing (2)
🧰 Additional context used🧠 Learnings (1)📚 Learning: 2025-12-18T02:50:37.669ZApplied to files:
🧬 Code graph analysis (2)input.go (2)
tscreen.go (2)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
🔇 Additional comments (8)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
tscreen.go (1)
1104-1145: Race condition:wg.Add(2)called after goroutines start.
t.wg.Add(2)at line 1145 is called after the goroutines are started at lines 1107-1108. BothinputLoopandmainLoophavedefer t.wg.Done()at the start. If either goroutine exits beforewg.Add(2)is called (e.g., immediate read error), it will callDone()on a zero-valued counter, causing a panic:sync: negative WaitGroup counter.🔎 Apply this diff to fix the race condition:
stopQ := make(chan struct{}) t.stopQ = stopQ + t.wg.Add(2) go t.inputLoop(stopQ) go t.mainLoop(stopQ) if !t.initted { t.Print(requestPrimaryDA) } t.processInitQ() t.running = true // ... rest of engage() ... t.tty.NotifyResize(t.resizeQ) - t.wg.Add(2) return nil
🧹 Nitpick comments (1)
tscreen.go (1)
314-330: Silent event dropping ifinitQis full.Lines 320-323 use a non-blocking send to
initQ. If the channel is full, theeventPrimaryAttributesevent is silently dropped. While this is unlikely to occur in practice (initQ has capacity 32), consider logging when this happens for debugging purposes.Additionally, the goroutine started here has no explicit shutdown mechanism. The channel
inQis never closed, so this goroutine will remain alive for the lifetime of the process. For a terminal application this is acceptable, but worth noting.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
input.go(4 hunks)tscreen.go(9 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-12-18T02:50:37.669Z
Learnt from: gdamore
Repo: gdamore/tcell PR: 908
File: stdin_unix.go:128-155
Timestamp: 2025-12-18T02:50:37.669Z
Learning: In the tcell Tty interface implementations (stdin_unix.go, tty_unix.go, tty_win.go, tty_plan9.go), the caller is responsible for providing synchronization/mutual exclusion guarantees for method calls like NotifyResize. The Tty implementations themselves do not need internal locking for these methods, as documented in tty.go. The tscreen.go layer handles the synchronization.
Applied to files:
tscreen.go
🧬 Code graph analysis (2)
input.go (2)
color.go (1)
Color(32-32)event.go (1)
EventTime(30-32)
tscreen.go (2)
event.go (1)
Event(23-26)color.go (2)
Color(32-32)ColorValid(42-42)
🔇 Additional comments (6)
input.go (2)
795-826: LGTM!The
handlePrimaryDAfunction correctly parses the primary device attributes response. The early return for empty params is appropriate, and the capability flag mapping aligns with standard DA attribute codes.
841-882: LGTM!The CSI handling changes correctly detect and route question-mark prefixed sequences. The pattern mirrors the existing
hasLThandling for mouse events, and the early return prevents fallthrough to standard CSI processing.tscreen.go (4)
103-104: LGTM!The new constants correctly define the primary DA request sequence and OSC 52 clipboard format.
251-279: LGTM!The initialization flow changes are well-structured. Setting up
initQandfilterEventsbeforeengage()ensures the event routing is ready when the input/main loops start. Moving the color palette initialization afterengage()correctly accounts for potentialncolorupdates from the DA response.
352-352: LGTM!Using the
setClipboardconstant instead of a hardcoded string improves consistency and maintainability.
284-312: Potential blocking issue: Lock held during 1-second timeout.The comment states this is called with the lock held, and indeed
engage()calls this while holdingt.Lock(). This function can block for up to 1 second waiting for the DA response, during which all other operations requiring thetScreenlock will be blocked.While the data flow for receiving the DA response should work (inputLoop → keychan → mainLoop → event parsing), any concurrent access to the screen (e.g., resize events, Show/Sync calls from other goroutines) will be blocked during initialization.
Consider releasing the lock before blocking on the channel, or restructuring to avoid holding the lock during the wait. For example:
func (t *tScreen) processInitQ() { t.Lock() if t.initted { t.Unlock() return } t.Unlock() expire := time.After(time.Second) for { select { case <-expire: t.Lock() t.initted = true t.Unlock() return case ev := <-t.initQ: // ... process event ... t.Lock() // ... update state ... t.initted = true t.Unlock() return } } }⛔ Skipped due to learnings
Learnt from: gdamore Repo: gdamore/tcell PR: 908 File: stdin_unix.go:128-155 Timestamp: 2025-12-18T02:50:37.669Z Learning: In the tcell Tty interface implementations (stdin_unix.go, tty_unix.go, tty_win.go, tty_plan9.go), the caller is responsible for providing synchronization/mutual exclusion guarantees for method calls like NotifyResize. The Tty implementations themselves do not need internal locking for these methods, as documented in tty.go. The tscreen.go layer handles the synchronization.
2829017 to
e7eb16b
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (1)
input.go (1)
1004-1004: Typo: "SerboCroation" should be "SerboCroatian".This also requires updating the corresponding assignment in
handlePrimaryDA(line 811).
🧹 Nitpick comments (1)
tscreen.go (1)
284-312: Potential blocking with lock held in processInitQ.The comment indicates this is "called with lock held", yet the function blocks for up to 1 second waiting on
initQ. During this time, any other goroutine attempting to acquiret.Lock()(e.g.,mainLoopon resize events) will be blocked.While this may be acceptable during initial startup (before external callers interact with the screen), consider either:
- Releasing the lock before blocking on the channel, or
- Using a shorter timeout with retry logic, or
- Documenting this behavior explicitly
🔎 Alternative approach releasing lock during wait:
func (t *tScreen) processInitQ() { - // NB: called with lock held if t.initted { return } expire := time.After(time.Second) + t.Unlock() + defer t.Lock() for { select { case <-expire: + t.Lock() t.initted = true + t.Unlock() return case ev := <-t.initQ: switch ev := ev.(type) { case *eventPrimaryAttributes: + t.Lock() if ev.Color && t.ncolor == 0 && !t.noColor { t.ncolor = 8 } if ev.Clipboard && t.setClipboard == "" { t.setClipboard = setClipboard } t.initted = true + t.Unlock() return } } } }
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
input.go(4 hunks)tscreen.go(9 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-12-18T02:50:37.669Z
Learnt from: gdamore
Repo: gdamore/tcell PR: 908
File: stdin_unix.go:128-155
Timestamp: 2025-12-18T02:50:37.669Z
Learning: In the tcell Tty interface implementations (stdin_unix.go, tty_unix.go, tty_win.go, tty_plan9.go), the caller is responsible for providing synchronization/mutual exclusion guarantees for method calls like NotifyResize. The Tty implementations themselves do not need internal locking for these methods, as documented in tty.go. The tscreen.go layer handles the synchronization.
Applied to files:
tscreen.go
🧬 Code graph analysis (2)
tscreen.go (2)
event.go (1)
Event(23-26)color.go (2)
Color(32-32)ColorValid(42-42)
input.go (1)
event.go (1)
EventTime(30-32)
🔇 Additional comments (7)
input.go (2)
795-826: LGTM on handlePrimaryDA parsing logic.The function correctly handles the DA response format, extracting the class from the first parameter and parsing capability flags for VT200+ terminals (class >= 60). The switch-case mapping of DA values to boolean flags is appropriate.
841-882: LGTM on QM-prefixed CSI handling.The addition of
hasQMhandling follows the existing pattern forhasLT, cleanly routing DA responses (?...csequences) tohandlePrimaryDAwhile preserving other CSI processing.tscreen.go (5)
103-104: LGTM on new escape sequence constants.The constants for
requestPrimaryDAandsetClipboardcentralize the escape sequences appropriately.
152-166: LGTM on new state fields.The
noColor,initQ, andinittedfields cleanly separate the initialization state from runtime state.
271-279: LGTM on deferred palette initialization.Moving the color palette setup after
engage()correctly allows the DA response to updatet.ncolorbefore the palette is built. This enables color support even when$TERMindicates a limited terminal.
1104-1145: LGTM on engage() restructuring.The revised flow correctly:
- Starts input/main loops first to receive DA responses
- Sends the DA request only if not already initialized
- Waits for initialization to complete
- Proceeds with the rest of terminal setup
The sequence ensures the DA response can be processed before capabilities are used.
352-352: LGTM on using setClipboard constant.This correctly uses the new constant instead of a duplicate hardcoded string.
This is just very preliminary for now, but it lets us detect color and OSC 52 even if the value of $TERM is vt100 or similar.
e7eb16b to
cb2c29d
Compare
This is just very preliminary for now, but it lets us detect color and OSC 52 even if the value of $TERM is vt100 or similar.
I will add support for other negotiations so we can pick up the underlying terminal name from extended attributes to configure things like OSC 9 on iTerm2, and we can avoid enabling signal handlers when the terminal supports inline size notifications.
Summary by CodeRabbit
New Features
Bug Fixes
✏️ Tip: You can customize this high-level summary in your review settings.