feat(terminal): show vim key table badge

This commit is contained in:
Brian Le 2026-03-09 00:07:00 -04:00
parent 0f29735635
commit 65bb81079b
No known key found for this signature in database

View file

@ -378,11 +378,27 @@ enum TerminalKeyboardCopyModeResolution: Equatable {
}
private let terminalKeyboardCopyModeMaxCount = 9_999
private let terminalKeyboardCopyModeIndicatorText = "vim"
private func terminalKeyboardCopyModeClampCount(_ value: Int) -> Int {
min(max(value, 1), terminalKeyboardCopyModeMaxCount)
}
private func terminalKeyTableIndicatorText(_ name: String) -> String {
let trimmed = name.trimmingCharacters(in: .whitespacesAndNewlines)
switch trimmed.lowercased() {
case "", "set":
return "key table"
case "vi", "vim":
return terminalKeyboardCopyModeIndicatorText
default:
let normalized = trimmed
.replacingOccurrences(of: "_", with: " ")
.replacingOccurrences(of: "-", with: " ")
return normalized.isEmpty ? "key table" : normalized
}
}
func terminalKeyboardCopyModeInitialViewportRow(
rows: Int,
imePointY: Double,
@ -2090,6 +2106,7 @@ final class TerminalSurface: Identifiable, ObservableObject {
}
@Published private(set) var keyboardCopyModeActive: Bool = false
private var searchNeedleCancellable: AnyCancellable?
var currentKeyStateIndicatorText: String? { surfaceView.currentKeyStateIndicatorText }
init(
tabId: UUID,
@ -2812,7 +2829,7 @@ final class TerminalSurface: Identifiable, ObservableObject {
if keyboardCopyModeActive != active {
keyboardCopyModeActive = active
}
hostedView.setKeyboardCopyModeIndicator(visible: active)
hostedView.syncKeyStateIndicator(text: surfaceView.currentKeyStateIndicatorText)
}
func hasSelection() -> Bool {
@ -2934,6 +2951,17 @@ class GhosttyNSView: NSView, NSUserInterfaceValidations {
/// movements should extend the selection (visual) or scroll the viewport.
private var keyboardCopyModeVisualActive = false
fileprivate var isKeyboardCopyModeActive: Bool { keyboardCopyModeActive }
fileprivate var currentKeyStateIndicatorText: String? {
if let name = keyTables.last {
return terminalKeyTableIndicatorText(name)
}
if keyboardCopyModeActive {
return terminalKeyboardCopyModeIndicatorText
}
return nil
}
#if DEBUG
private static let keyLatencyProbeEnabled: Bool = {
if ProcessInfo.processInfo.environment["CMUX_KEY_LATENCY_PROBE"] == "1" {
@ -4415,6 +4443,8 @@ class GhosttyNSView: NSView, NSUserInterfaceValidations {
default:
break
}
terminalSurface?.hostedView.syncKeyStateIndicator(text: currentKeyStateIndicatorText)
}
// MARK: - Mouse Handling
@ -4917,6 +4947,7 @@ final class GhosttySurfaceScrollView: NSView {
private let flashOverlayView: GhosttyFlashOverlayView
private let flashLayer: CAShapeLayer
private let keyboardCopyModeBadgeView: GhosttyPassthroughVisualEffectView
private let keyboardCopyModeBadgeIconView: NSImageView
private let keyboardCopyModeBadgeLabel: NSTextField
private var searchOverlayHostingView: NSHostingView<SurfaceSearchOverlay>?
private var lastSearchOverlayStateID: ObjectIdentifier?
@ -5085,7 +5116,8 @@ final class GhosttySurfaceScrollView: NSView {
flashOverlayView = GhosttyFlashOverlayView(frame: .zero)
flashLayer = CAShapeLayer()
keyboardCopyModeBadgeView = GhosttyPassthroughVisualEffectView(frame: .zero)
keyboardCopyModeBadgeLabel = NSTextField(labelWithString: "VI MODE")
keyboardCopyModeBadgeIconView = NSImageView(frame: .zero)
keyboardCopyModeBadgeLabel = NSTextField(labelWithString: terminalKeyboardCopyModeIndicatorText)
scrollView.hasVerticalScroller = true
scrollView.hasHorizontalScroller = false
scrollView.autohidesScrollers = false
@ -5163,20 +5195,41 @@ final class GhosttySurfaceScrollView: NSView {
keyboardCopyModeBadgeView.material = .hudWindow
keyboardCopyModeBadgeView.blendingMode = .withinWindow
keyboardCopyModeBadgeView.state = .active
keyboardCopyModeBadgeView.layer?.cornerRadius = 7
keyboardCopyModeBadgeView.layer?.cornerRadius = 18
keyboardCopyModeBadgeView.layer?.masksToBounds = true
keyboardCopyModeBadgeView.layer?.borderWidth = 1
keyboardCopyModeBadgeView.layer?.borderColor = cmuxAccentNSColor().withAlphaComponent(0.45).cgColor
keyboardCopyModeBadgeView.layer?.borderColor = NSColor.white.withAlphaComponent(0.12).cgColor
keyboardCopyModeBadgeView.layer?.shadowColor = NSColor.black.cgColor
keyboardCopyModeBadgeView.layer?.shadowOpacity = 0.22
keyboardCopyModeBadgeView.layer?.shadowRadius = 10
keyboardCopyModeBadgeView.layer?.shadowOffset = CGSize(width: 0, height: 2)
keyboardCopyModeBadgeView.alphaValue = 0.97
keyboardCopyModeBadgeIconView.translatesAutoresizingMaskIntoConstraints = false
keyboardCopyModeBadgeIconView.symbolConfiguration = NSImage.SymbolConfiguration(
pointSize: 13,
weight: .regular,
scale: .medium
)
keyboardCopyModeBadgeIconView.image = NSImage(
systemSymbolName: "keyboard.badge.ellipsis",
accessibilityDescription: "Key table"
)
keyboardCopyModeBadgeIconView.contentTintColor = NSColor.secondaryLabelColor
keyboardCopyModeBadgeLabel.translatesAutoresizingMaskIntoConstraints = false
keyboardCopyModeBadgeLabel.textColor = NSColor.labelColor
keyboardCopyModeBadgeLabel.font = NSFont.systemFont(ofSize: 13, weight: .semibold)
keyboardCopyModeBadgeLabel.lineBreakMode = .byClipping
keyboardCopyModeBadgeView.addSubview(keyboardCopyModeBadgeIconView)
keyboardCopyModeBadgeView.addSubview(keyboardCopyModeBadgeLabel)
NSLayoutConstraint.activate([
keyboardCopyModeBadgeLabel.leadingAnchor.constraint(equalTo: keyboardCopyModeBadgeView.leadingAnchor, constant: 8),
keyboardCopyModeBadgeLabel.trailingAnchor.constraint(equalTo: keyboardCopyModeBadgeView.trailingAnchor, constant: -8),
keyboardCopyModeBadgeLabel.topAnchor.constraint(equalTo: keyboardCopyModeBadgeView.topAnchor, constant: 4),
keyboardCopyModeBadgeLabel.bottomAnchor.constraint(equalTo: keyboardCopyModeBadgeView.bottomAnchor, constant: -4),
keyboardCopyModeBadgeIconView.leadingAnchor.constraint(equalTo: keyboardCopyModeBadgeView.leadingAnchor, constant: 12),
keyboardCopyModeBadgeIconView.centerYAnchor.constraint(equalTo: keyboardCopyModeBadgeView.centerYAnchor),
keyboardCopyModeBadgeIconView.widthAnchor.constraint(equalToConstant: 18),
keyboardCopyModeBadgeIconView.heightAnchor.constraint(equalToConstant: 18),
keyboardCopyModeBadgeLabel.leadingAnchor.constraint(equalTo: keyboardCopyModeBadgeIconView.trailingAnchor, constant: 7),
keyboardCopyModeBadgeLabel.trailingAnchor.constraint(equalTo: keyboardCopyModeBadgeView.trailingAnchor, constant: -14),
keyboardCopyModeBadgeLabel.topAnchor.constraint(equalTo: keyboardCopyModeBadgeView.topAnchor, constant: 8),
keyboardCopyModeBadgeLabel.bottomAnchor.constraint(equalTo: keyboardCopyModeBadgeView.bottomAnchor, constant: -8),
])
keyboardCopyModeBadgeView.isHidden = true
addSubview(keyboardCopyModeBadgeView)
@ -5603,22 +5656,26 @@ final class GhosttySurfaceScrollView: NSView {
lastSearchOverlayStateID = searchStateID
}
func setKeyboardCopyModeIndicator(visible: Bool) {
func syncKeyStateIndicator(text: String?) {
if !Thread.isMainThread {
DispatchQueue.main.async { [weak self] in
self?.setKeyboardCopyModeIndicator(visible: visible)
self?.syncKeyStateIndicator(text: text)
}
return
}
keyboardCopyModeBadgeView.isHidden = !visible
if visible {
if let text, !text.isEmpty {
keyboardCopyModeBadgeLabel.stringValue = text
keyboardCopyModeBadgeView.isHidden = false
if let overlay = searchOverlayHostingView {
addSubview(keyboardCopyModeBadgeView, positioned: .above, relativeTo: overlay)
} else {
addSubview(keyboardCopyModeBadgeView, positioned: .above, relativeTo: nil)
}
return
}
keyboardCopyModeBadgeView.isHidden = true
}
private func dropZoneOverlayFrame(for zone: DropZone, in size: CGSize) -> CGRect {
@ -6947,7 +7004,7 @@ struct GhosttyTerminalView: NSViewRepresentable {
)
hostedView.setNotificationRing(visible: showsUnreadNotificationRing)
hostedView.setSearchOverlay(searchState: searchState)
hostedView.setKeyboardCopyModeIndicator(visible: terminalSurface.keyboardCopyModeActive)
hostedView.syncKeyStateIndicator(text: terminalSurface.currentKeyStateIndicatorText)
hostedView.setFocusHandler { onFocus?(terminalSurface.id) }
hostedView.setTriggerFlashHandler(onTriggerFlash)
let portalExpectedSurfaceId = terminalSurface.id