Skip to content

Commit 5958c03

Browse files
committed
Use inputWithModifiers for terminal input
Replaces the call to term.input with inputWithModifiers in the type(text:) method to handle input with modifier keys. This may improve support for complex input scenarios in the terminal. Add lock functionality for Ctrl and Alt modifiers in terminal Introduces the ability to lock the Control and Alt modifier keys in the terminal keyboard toolbar via double-tap, with visual feedback and state management. Updates Swift and JavaScript code to support locked states, ensuring modifiers remain active until explicitly unlocked, and modifies reset logic to respect locked states.
1 parent ea7a2b9 commit 5958c03

3 files changed

Lines changed: 110 additions & 17 deletions

File tree

CodeApp/Managers/TerminalInstance.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -540,7 +540,7 @@ extension TerminalInstance: WKUIDelegate {
540540
extension TerminalInstance {
541541
func type(text: String) {
542542
guard let base64 = text.base64Encoded() else { return }
543-
executeScript("term.input(base64ToString(`\(base64)`))")
543+
executeScript("inputWithModifiers(base64ToString(`\(base64)`))")
544544
}
545545

546546
func moveCursor(codeSequence: String) {
@@ -551,9 +551,17 @@ extension TerminalInstance {
551551
executeScript("setControlActive(\(active), \(generation))")
552552
}
553553

554+
func setControlLocked(_ locked: Bool) {
555+
executeScript("setControlLocked(\(locked))")
556+
}
557+
554558
func setAltActive(_ active: Bool, generation: Int) {
555559
executeScript("setAltActive(\(active), \(generation))")
556560
}
561+
562+
func setAltLocked(_ locked: Bool) {
563+
executeScript("setAltLocked(\(locked))")
564+
}
557565
}
558566

559567
extension Notification.Name {

CodeApp/Views/TerminalKeyboardToolbar.swift

Lines changed: 87 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,25 +13,97 @@ struct TerminalKeyboardToolBar: View {
1313
@Environment(\.horizontalSizeClass) var horizontalSizeClass
1414
@State var pasteBoardHasContent = false
1515
@State var controlActive = false
16+
@State var controlLocked = false
17+
@State var controlLastTapTime: Date?
1618
@State var controlGeneration = 0
1719
@State var altActive = false
20+
@State var altLocked = false
21+
@State var altLastTapTime: Date?
1822
@State var altGeneration = 0
1923

24+
private let doubleTapInterval: TimeInterval = 0.3
25+
2026
private func resetModifierStates() {
2127
controlActive = false
28+
controlLocked = false
2229
App.terminalInstance.setControlActive(false, generation: controlGeneration)
30+
App.terminalInstance.setControlLocked(false)
2331
altActive = false
32+
altLocked = false
2433
App.terminalInstance.setAltActive(false, generation: altGeneration)
34+
App.terminalInstance.setAltLocked(false)
35+
}
36+
37+
private func resetUnlockedModifiers() {
38+
// Reset modifiers only if they are not locked
39+
if !controlLocked {
40+
controlActive = false
41+
App.terminalInstance.setControlActive(false, generation: controlGeneration)
42+
}
43+
if !altLocked {
44+
altActive = false
45+
App.terminalInstance.setAltActive(false, generation: altGeneration)
46+
}
47+
}
48+
49+
private func handleControlTap() {
50+
let now = Date()
51+
let isDoubleTap =
52+
controlLastTapTime.map { now.timeIntervalSince($0) < doubleTapInterval } ?? false
53+
controlLastTapTime = now
54+
55+
if controlLocked {
56+
// Single tap while locked: unlock and deactivate
57+
controlLocked = false
58+
controlActive = false
59+
controlGeneration += 1
60+
App.terminalInstance.setControlLocked(false)
61+
App.terminalInstance.setControlActive(false, generation: controlGeneration)
62+
} else if isDoubleTap && controlActive {
63+
// Double tap while active: lock
64+
controlLocked = true
65+
App.terminalInstance.setControlLocked(true)
66+
} else {
67+
// Single tap: toggle active state
68+
controlActive.toggle()
69+
controlGeneration += 1
70+
App.terminalInstance.setControlActive(controlActive, generation: controlGeneration)
71+
}
72+
}
73+
74+
private func handleAltTap() {
75+
let now = Date()
76+
let isDoubleTap =
77+
altLastTapTime.map { now.timeIntervalSince($0) < doubleTapInterval } ?? false
78+
altLastTapTime = now
79+
80+
if altLocked {
81+
// Single tap while locked: unlock and deactivate
82+
altLocked = false
83+
altActive = false
84+
altGeneration += 1
85+
App.terminalInstance.setAltLocked(false)
86+
App.terminalInstance.setAltActive(false, generation: altGeneration)
87+
} else if isDoubleTap && altActive {
88+
// Double tap while active: lock
89+
altLocked = true
90+
App.terminalInstance.setAltLocked(true)
91+
} else {
92+
// Single tap: toggle active state
93+
altActive.toggle()
94+
altGeneration += 1
95+
App.terminalInstance.setAltActive(altActive, generation: altGeneration)
96+
}
2597
}
2698

2799
private func typeAndResetModifiers(text: String) {
28100
App.terminalInstance.type(text: text)
29-
resetModifierStates()
101+
resetUnlockedModifiers()
30102
}
31103

32104
private func moveCursorAndResetModifiers(codeSequence: String) {
33105
App.terminalInstance.moveCursor(codeSequence: codeSequence)
34-
resetModifierStates()
106+
resetUnlockedModifiers()
35107
}
36108

37109
var body: some View {
@@ -66,10 +138,7 @@ struct TerminalKeyboardToolBar: View {
66138
})
67139
Button(
68140
action: {
69-
controlActive.toggle()
70-
controlGeneration += 1
71-
App.terminalInstance.setControlActive(
72-
controlActive, generation: controlGeneration)
141+
handleControlTap()
73142
},
74143
label: {
75144
Text("Ctrl")
@@ -78,16 +147,18 @@ struct TerminalKeyboardToolBar: View {
78147
controlActive ? Color.accentColor.opacity(0.3) : Color.clear
79148
)
80149
.cornerRadius(4)
150+
.overlay(
151+
RoundedRectangle(cornerRadius: 4)
152+
.stroke(Color.accentColor, lineWidth: controlLocked ? 2 : 0)
153+
)
81154
}
82155
)
83156
.accessibilityLabel("Control")
84-
.accessibilityValue(controlActive ? "Active" : "Inactive")
157+
.accessibilityValue(
158+
controlLocked ? "Locked" : (controlActive ? "Active" : "Inactive"))
85159
Button(
86160
action: {
87-
altActive.toggle()
88-
altGeneration += 1
89-
App.terminalInstance.setAltActive(
90-
altActive, generation: altGeneration)
161+
handleAltTap()
91162
},
92163
label: {
93164
Text("Alt")
@@ -96,10 +167,14 @@ struct TerminalKeyboardToolBar: View {
96167
altActive ? Color.accentColor.opacity(0.3) : Color.clear
97168
)
98169
.cornerRadius(4)
170+
.overlay(
171+
RoundedRectangle(cornerRadius: 4)
172+
.stroke(Color.accentColor, lineWidth: altLocked ? 2 : 0)
173+
)
99174
}
100175
)
101176
.accessibilityLabel("Alt")
102-
.accessibilityValue(altActive ? "Active" : "Inactive")
177+
.accessibilityValue(altLocked ? "Locked" : (altActive ? "Active" : "Inactive"))
103178
Button(
104179
action: {
105180
typeAndResetModifiers(text: "\u{1b}[3~")

Dependencies/terminal.bundle/index.html

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -155,20 +155,30 @@
155155
localEcho.addAutocompleteHandler(autocompleteCommonFiles);
156156

157157
var controlActive = false;
158+
var controlLocked = false;
158159
var controlGeneration = 0;
159160
var altActive = false;
161+
var altLocked = false;
160162
var altGeneration = 0;
161163

162164
function setControlActive(active, generation) {
163165
controlActive = active;
164166
controlGeneration = generation;
165167
}
166168

169+
function setControlLocked(locked) {
170+
controlLocked = locked;
171+
}
172+
167173
function setAltActive(active, generation) {
168174
altActive = active;
169175
altGeneration = generation;
170176
}
171177

178+
function setAltLocked(locked) {
179+
altLocked = locked;
180+
}
181+
172182
function shouldApplyModifierToCsi(final, params) {
173183
if (final >= "A" && final <= "D") {
174184
return true;
@@ -245,23 +255,23 @@
245255
}
246256

247257
function applyModifierStates(data) {
248-
// Capture which modifiers are active before resetting
258+
// Capture which modifiers are active before potentially resetting
249259
var wasControlActive = controlActive;
250260
var wasAltActive = altActive;
251261

252262
if (!wasControlActive && !wasAltActive) {
253263
return data;
254264
}
255265

256-
// Reset all active modifiers and notify Swift
257-
if (wasControlActive) {
266+
// Reset modifiers only if not locked, and notify Swift
267+
if (wasControlActive && !controlLocked) {
258268
controlActive = false;
259269
window.webkit.messageHandlers.toggleMessageHandler2.postMessage({
260270
Event: "ControlReset",
261271
Generation: controlGeneration,
262272
});
263273
}
264-
if (wasAltActive) {
274+
if (wasAltActive && !altLocked) {
265275
altActive = false;
266276
window.webkit.messageHandlers.toggleMessageHandler2.postMessage({
267277
Event: "AltReset",

0 commit comments

Comments
 (0)