Skip to content

Commit a1d4da4

Browse files
Terminal Tabs Closing Wrong Tab, Terminal Right Click Semantics (#1963)
1 parent 580b249 commit a1d4da4

14 files changed

+364
-86
lines changed

CodeEdit.xcodeproj/project.pbxproj

+19-4
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,7 @@
404404
6C4E37F62C73DA5200AEE7B5 /* UtilityAreaTerminalSidebar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C4E37F52C73DA5200AEE7B5 /* UtilityAreaTerminalSidebar.swift */; };
405405
6C4E37FC2C73E00700AEE7B5 /* SwiftTerm in Frameworks */ = {isa = PBXBuildFile; productRef = 6C4E37FB2C73E00700AEE7B5 /* SwiftTerm */; };
406406
6C510CB82D2E4639006EBE85 /* XCUITest+waitForNonExistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C510CB72D2E4639006EBE85 /* XCUITest+waitForNonExistence.swift */; };
407+
6C510CBC2D2ECD68006EBE85 /* UtilityAreaViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C510CBA2D2ECD68006EBE85 /* UtilityAreaViewModelTests.swift */; };
407408
6C5228B529A868BD00AC48F6 /* Environment+ContentInsets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C5228B429A868BD00AC48F6 /* Environment+ContentInsets.swift */; };
408409
6C53AAD829A6C4FD00EE9ED6 /* SplitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C53AAD729A6C4FD00EE9ED6 /* SplitView.swift */; };
409410
6C578D8129CD294800DC73B2 /* ExtensionActivatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C578D8029CD294800DC73B2 /* ExtensionActivatorView.swift */; };
@@ -467,6 +468,7 @@
467468
6CC3D1FB2D1475EC00822B65 /* TextView+SemanticTokenRangeProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CC3D1FA2D1475EC00822B65 /* TextView+SemanticTokenRangeProvider.swift */; };
468469
6CC3D1FD2D14761A00822B65 /* SemanticTokenMapRangeProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CC3D1FC2D14761A00822B65 /* SemanticTokenMapRangeProvider.swift */; };
469470
6CC9E4B229B5669900C97388 /* Environment+ActiveEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CC9E4B129B5669900C97388 /* Environment+ActiveEditor.swift */; };
471+
6CCEE7F52D2C91F700B2B854 /* UtilityAreaTerminalPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CCEE7F42D2C91F700B2B854 /* UtilityAreaTerminalPicker.swift */; };
470472
6CD0358A2C3461160091E1F4 /* KeyWindowControllerObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CD035892C3461160091E1F4 /* KeyWindowControllerObserver.swift */; };
471473
6CD03B6A29FC773F001BD1D0 /* SettingsInjector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CD03B6929FC773F001BD1D0 /* SettingsInjector.swift */; };
472474
6CD26C6E2C8EA1E600ADBA38 /* LanguageServerFileMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CD26C6D2C8EA1E600ADBA38 /* LanguageServerFileMap.swift */; };
@@ -1098,6 +1100,7 @@
10981100
6C48D8F62972E5F300D6D205 /* WindowObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowObserver.swift; sourceTree = "<group>"; };
10991101
6C4E37F52C73DA5200AEE7B5 /* UtilityAreaTerminalSidebar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UtilityAreaTerminalSidebar.swift; sourceTree = "<group>"; };
11001102
6C510CB72D2E4639006EBE85 /* XCUITest+waitForNonExistence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCUITest+waitForNonExistence.swift"; sourceTree = "<group>"; };
1103+
6C510CBA2D2ECD68006EBE85 /* UtilityAreaViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UtilityAreaViewModelTests.swift; sourceTree = "<group>"; };
11011104
6C5228B429A868BD00AC48F6 /* Environment+ContentInsets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Environment+ContentInsets.swift"; sourceTree = "<group>"; };
11021105
6C53AAD729A6C4FD00EE9ED6 /* SplitView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitView.swift; sourceTree = "<group>"; };
11031106
6C578D8029CD294800DC73B2 /* ExtensionActivatorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExtensionActivatorView.swift; sourceTree = "<group>"; };
@@ -1147,6 +1150,7 @@
11471150
6CC3D1FA2D1475EC00822B65 /* TextView+SemanticTokenRangeProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TextView+SemanticTokenRangeProvider.swift"; sourceTree = "<group>"; };
11481151
6CC3D1FC2D14761A00822B65 /* SemanticTokenMapRangeProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SemanticTokenMapRangeProvider.swift; sourceTree = "<group>"; };
11491152
6CC9E4B129B5669900C97388 /* Environment+ActiveEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Environment+ActiveEditor.swift"; sourceTree = "<group>"; };
1153+
6CCEE7F42D2C91F700B2B854 /* UtilityAreaTerminalPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UtilityAreaTerminalPicker.swift; sourceTree = "<group>"; };
11501154
6CD035892C3461160091E1F4 /* KeyWindowControllerObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyWindowControllerObserver.swift; sourceTree = "<group>"; };
11511155
6CD03B6929FC773F001BD1D0 /* SettingsInjector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsInjector.swift; sourceTree = "<group>"; };
11521156
6CD26C6D2C8EA1E600ADBA38 /* LanguageServerFileMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LanguageServerFileMap.swift; sourceTree = "<group>"; };
@@ -2070,6 +2074,7 @@
20702074
613899BD2B6E70E200A5CAF6 /* Search */,
20712075
61FB03A92C3C1FC4001B3671 /* Tasks */,
20722076
6141CF392C3DA4180073BC9F /* TerminalEmulator */,
2077+
6C510CBB2D2ECD68006EBE85 /* UtilityArea */,
20732078
);
20742079
path = Features;
20752080
sourceTree = "<group>";
@@ -2678,8 +2683,7 @@
26782683
6C1F3DA12C18C55800F6DEF6 /* ShellIntegrationTests.swift */,
26792684
61FB03AA2C3C1FD5001B3671 /* Shell */,
26802685
);
2681-
name = TerminalEmulator;
2682-
path = ActivityViewer/TerminalEmulator;
2686+
path = TerminalEmulator;
26832687
sourceTree = "<group>";
26842688
};
26852689
617DB3CE2C25AF5B00B58BFE /* ActivityViewer */ = {
@@ -2979,6 +2983,14 @@
29792983
path = Extensions;
29802984
sourceTree = "<group>";
29812985
};
2986+
6C510CBB2D2ECD68006EBE85 /* UtilityArea */ = {
2987+
isa = PBXGroup;
2988+
children = (
2989+
6C510CBA2D2ECD68006EBE85 /* UtilityAreaViewModelTests.swift */,
2990+
);
2991+
path = UtilityArea;
2992+
sourceTree = "<group>";
2993+
};
29822994
6C6BD6ED29CD123000235D17 /* Extensions */ = {
29832995
isa = PBXGroup;
29842996
children = (
@@ -3473,9 +3485,10 @@
34733485
B676606A2AA973A500CD56B0 /* TerminalUtility */ = {
34743486
isa = PBXGroup;
34753487
children = (
3476-
B62AEDB22A1FD95B009A9F52 /* UtilityAreaTerminalView.swift */,
3477-
B62AEDBB2A210DBB009A9F52 /* UtilityAreaTerminalTab.swift */,
3488+
6CCEE7F42D2C91F700B2B854 /* UtilityAreaTerminalPicker.swift */,
34783489
6C4E37F52C73DA5200AEE7B5 /* UtilityAreaTerminalSidebar.swift */,
3490+
B62AEDBB2A210DBB009A9F52 /* UtilityAreaTerminalTab.swift */,
3491+
B62AEDB22A1FD95B009A9F52 /* UtilityAreaTerminalView.swift */,
34793492
);
34803493
path = TerminalUtility;
34813494
sourceTree = "<group>";
@@ -4553,6 +4566,7 @@
45534566
613899BC2B6E709C00A5CAF6 /* URL+FuzzySearchable.swift in Sources */,
45544567
611192002B08CCD700D4459B /* SearchIndexer+Memory.swift in Sources */,
45554568
587B9E8129301D8F00AC7927 /* PublicKey.swift in Sources */,
4569+
6CCEE7F52D2C91F700B2B854 /* UtilityAreaTerminalPicker.swift in Sources */,
45564570
611191FE2B08CCD200D4459B /* SearchIndexer+File.swift in Sources */,
45574571
B69D3EE52C5F54B3005CF43A /* TasksPopoverMenuItem.swift in Sources */,
45584572
669A50532C380C8E00304CD8 /* Collection+subscript_safe.swift in Sources */,
@@ -4585,6 +4599,7 @@
45854599
583E528C29361B39001AB554 /* CodeEditUITests.swift in Sources */,
45864600
6C7D6D462C9092EC00B69EE0 /* BufferingServerConnection.swift in Sources */,
45874601
613053652B23A49300D767E3 /* TemporaryFile.swift in Sources */,
4602+
6C510CBC2D2ECD68006EBE85 /* UtilityAreaViewModelTests.swift in Sources */,
45884603
617DB3DF2C25E13800B58BFE /* TaskNotificationHandlerTests.swift in Sources */,
45894604
775566502C27FD1B001E7A4D /* CodeFileDocument+UTTypeTests.swift in Sources */,
45904605
587B60F82934124200D5CD8F /* CEWorkspaceFileManagerTests.swift in Sources */,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<Scheme
3+
LastUpgradeVersion = "1620"
4+
wasCreatedForAppExtension = "YES"
5+
version = "2.0">
6+
<BuildAction
7+
parallelizeBuildables = "YES"
8+
buildImplicitDependencies = "YES"
9+
buildArchitectures = "Automatic">
10+
<BuildActionEntries>
11+
<BuildActionEntry
12+
buildForTesting = "YES"
13+
buildForRunning = "YES"
14+
buildForProfiling = "YES"
15+
buildForArchiving = "YES"
16+
buildForAnalyzing = "YES">
17+
<BuildableReference
18+
BuildableIdentifier = "primary"
19+
BlueprintIdentifier = "2BE487EB28245162003F3F64"
20+
BuildableName = "OpenWithCodeEdit.appex"
21+
BlueprintName = "OpenWithCodeEdit"
22+
ReferencedContainer = "container:CodeEdit.xcodeproj">
23+
</BuildableReference>
24+
</BuildActionEntry>
25+
<BuildActionEntry
26+
buildForTesting = "YES"
27+
buildForRunning = "YES"
28+
buildForProfiling = "YES"
29+
buildForArchiving = "YES"
30+
buildForAnalyzing = "YES">
31+
<BuildableReference
32+
BuildableIdentifier = "primary"
33+
BlueprintIdentifier = "B658FB2B27DA9E0F00EA4DBD"
34+
BuildableName = "CodeEdit.app"
35+
BlueprintName = "CodeEdit"
36+
ReferencedContainer = "container:CodeEdit.xcodeproj">
37+
</BuildableReference>
38+
</BuildActionEntry>
39+
</BuildActionEntries>
40+
</BuildAction>
41+
<TestAction
42+
buildConfiguration = "Debug"
43+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
44+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
45+
shouldUseLaunchSchemeArgsEnv = "YES"
46+
shouldAutocreateTestPlan = "YES">
47+
</TestAction>
48+
<LaunchAction
49+
buildConfiguration = "Debug"
50+
selectedDebuggerIdentifier = ""
51+
selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
52+
launchStyle = "0"
53+
askForAppToLaunch = "Yes"
54+
useCustomWorkingDirectory = "NO"
55+
ignoresPersistentStateOnLaunch = "NO"
56+
debugDocumentVersioning = "YES"
57+
debugServiceExtension = "internal"
58+
allowLocationSimulation = "YES"
59+
launchAutomaticallySubstyle = "2">
60+
<BuildableProductRunnable
61+
runnableDebuggingMode = "0">
62+
<BuildableReference
63+
BuildableIdentifier = "primary"
64+
BlueprintIdentifier = "B658FB2B27DA9E0F00EA4DBD"
65+
BuildableName = "CodeEdit.app"
66+
BlueprintName = "CodeEdit"
67+
ReferencedContainer = "container:CodeEdit.xcodeproj">
68+
</BuildableReference>
69+
</BuildableProductRunnable>
70+
</LaunchAction>
71+
<ProfileAction
72+
buildConfiguration = "Release"
73+
shouldUseLaunchSchemeArgsEnv = "YES"
74+
savedToolIdentifier = ""
75+
useCustomWorkingDirectory = "NO"
76+
debugDocumentVersioning = "YES"
77+
askForAppToLaunch = "Yes"
78+
launchAutomaticallySubstyle = "2">
79+
<BuildableProductRunnable
80+
runnableDebuggingMode = "0">
81+
<BuildableReference
82+
BuildableIdentifier = "primary"
83+
BlueprintIdentifier = "B658FB2B27DA9E0F00EA4DBD"
84+
BuildableName = "CodeEdit.app"
85+
BlueprintName = "CodeEdit"
86+
ReferencedContainer = "container:CodeEdit.xcodeproj">
87+
</BuildableReference>
88+
</BuildableProductRunnable>
89+
</ProfileAction>
90+
<AnalyzeAction
91+
buildConfiguration = "Debug">
92+
</AnalyzeAction>
93+
<ArchiveAction
94+
buildConfiguration = "Release"
95+
revealArchiveInOrganizer = "YES">
96+
</ArchiveAction>
97+
</Scheme>

CodeEdit/Features/SplitView/Views/SplitView.swift

+1
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,6 @@ struct SplitView<Content: View>: View {
2525
}
2626
}
2727
._trait(SplitViewControllerLayoutValueKey.self, viewController)
28+
.accessibilityElement(children: .contain)
2829
}
2930
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
//
2+
// UtilityAreaTerminalPicker.swift
3+
// CodeEdit
4+
//
5+
// Created by Khan Winter on 1/6/25.
6+
//
7+
8+
import SwiftUI
9+
10+
struct UtilityAreaTerminalPicker: View {
11+
@Binding var selectedIDs: Set<UUID>
12+
var terminals: [UtilityAreaTerminal]
13+
14+
var selectedID: Binding<UUID?> {
15+
Binding<UUID?>(
16+
get: {
17+
selectedIDs.first
18+
},
19+
set: { newValue in
20+
if let selectedID = newValue {
21+
selectedIDs = [selectedID]
22+
}
23+
}
24+
)
25+
}
26+
27+
var body: some View {
28+
Picker("Terminal Tab", selection: selectedID) {
29+
ForEach(terminals, id: \.self.id) { terminal in
30+
Text(terminal.title)
31+
.tag(terminal.id)
32+
}
33+
34+
if terminals.isEmpty {
35+
Text("No Open Terminals")
36+
}
37+
}
38+
.labelsHidden()
39+
.controlSize(.small)
40+
.buttonStyle(.borderless)
41+
}
42+
}

CodeEdit/Features/UtilityArea/TerminalUtility/UtilityAreaTerminalSidebar.swift

+8-6
Original file line numberDiff line numberDiff line change
@@ -26,37 +26,37 @@ struct UtilityAreaTerminalSidebar: View {
2626
.listRowSeparator(.hidden)
2727
}
2828
.onMove { [weak utilityAreaViewModel] (source, destination) in
29-
utilityAreaViewModel?.moveItems(from: source, to: destination)
29+
utilityAreaViewModel?.reorderTerminals(from: source, to: destination)
3030
}
3131
}
3232
.focusedObject(utilityAreaViewModel)
3333
.listStyle(.automatic)
3434
.accentColor(.secondary)
3535
.contextMenu {
3636
Button("New Terminal") {
37-
utilityAreaViewModel.addTerminal(workspace: workspace)
37+
utilityAreaViewModel.addTerminal(rootURL: workspace.fileURL)
3838
}
3939
Menu("New Terminal With Profile") {
4040
Button("Default") {
41-
utilityAreaViewModel.addTerminal(workspace: workspace)
41+
utilityAreaViewModel.addTerminal(rootURL: workspace.fileURL)
4242
}
4343
Divider()
4444
ForEach(Shell.allCases, id: \.self) { shell in
4545
Button(shell.rawValue) {
46-
utilityAreaViewModel.addTerminal(shell: shell, workspace: workspace)
46+
utilityAreaViewModel.addTerminal(shell: shell, rootURL: workspace.fileURL)
4747
}
4848
}
4949
}
5050
}
5151
.onChange(of: utilityAreaViewModel.terminals) { newValue in
5252
if newValue.isEmpty {
53-
utilityAreaViewModel.addTerminal(workspace: workspace)
53+
utilityAreaViewModel.addTerminal(rootURL: workspace.fileURL)
5454
}
5555
}
5656
.paneToolbar {
5757
PaneToolbarSection {
5858
Button {
59-
utilityAreaViewModel.addTerminal(workspace: workspace)
59+
utilityAreaViewModel.addTerminal(rootURL: workspace.fileURL)
6060
} label: {
6161
Image(systemName: "plus")
6262
}
@@ -70,6 +70,8 @@ struct UtilityAreaTerminalSidebar: View {
7070
}
7171
Spacer()
7272
}
73+
.accessibilityElement(children: .contain)
74+
.accessibilityLabel("Terminals")
7375
}
7476
}
7577

CodeEdit/Features/UtilityArea/TerminalUtility/UtilityAreaTerminalTab.swift

+10-2
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,21 @@ struct UtilityAreaTerminalTab: View {
4646
}
4747
} icon: {
4848
Image(systemName: "terminal")
49+
.accessibilityHidden(true)
4950
}
5051
.contextMenu {
5152
Button("Rename...") {
5253
isFocused = true
5354
}
54-
Button("Kill Terminal") {
55-
if isSelected { removeTerminals([terminal.id]) }
55+
56+
if selectedIDs.contains(terminal.id) && selectedIDs.count > 1 {
57+
Button("Kill Terminals") {
58+
removeTerminals(selectedIDs)
59+
}
60+
} else {
61+
Button("Kill Terminal") {
62+
removeTerminals([terminal.id])
63+
}
5664
}
5765
}
5866
}

CodeEdit/Features/UtilityArea/TerminalUtility/UtilityAreaTerminalView.swift

+6-32
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ struct UtilityAreaTerminalView: View {
138138
guard let terminal = getSelectedTerminal() else {
139139
return
140140
}
141-
utilityAreaViewModel.addTerminal(shell: nil, workspace: workspace, replacing: terminal.id)
141+
utilityAreaViewModel.replaceTerminal(terminal.id)
142142
} label: {
143143
Image(systemName: "trash")
144144
}
@@ -161,7 +161,11 @@ struct UtilityAreaTerminalView: View {
161161
UtilityAreaTerminalSidebar()
162162
}
163163
.onAppear {
164-
utilityAreaViewModel.initializeTerminals(workspace)
164+
guard let workspaceURL = workspace.fileURL else {
165+
assertionFailure("Workspace does not have a file URL.")
166+
return
167+
}
168+
utilityAreaViewModel.initializeTerminals(workspaceURL: workspaceURL)
165169
}
166170
}
167171

@@ -179,33 +183,3 @@ struct UtilityAreaTerminalView: View {
179183
}
180184
}
181185
}
182-
183-
struct UtilityAreaTerminalPicker: View {
184-
@Binding var selectedIDs: Set<UUID>
185-
var terminals: [UtilityAreaTerminal]
186-
187-
var selectedID: Binding<UUID?> {
188-
Binding<UUID?>(
189-
get: {
190-
selectedIDs.first
191-
},
192-
set: { newValue in
193-
if let selectedID = newValue {
194-
selectedIDs = [selectedID]
195-
}
196-
}
197-
)
198-
}
199-
200-
var body: some View {
201-
Picker("Terminal Tab", selection: selectedID) {
202-
ForEach(terminals, id: \.self.id) { terminal in
203-
Text(terminal.title)
204-
.tag(terminal.id as UUID?)
205-
}
206-
}
207-
.labelsHidden()
208-
.controlSize(.small)
209-
.buttonStyle(.borderless)
210-
}
211-
}

0 commit comments

Comments
 (0)