Skip to content

Commit 5672f2b

Browse files
Send SIGINT to terminals on workspace close, UI polish
- Send SIGINT to all terminal process groups when a workspace is closed so child processes (servers, Claude) release ports and clean up. - PR badge in workspace list: "PR #123 (Merged)" without dot. - Delete menu says "Delete Workspace", modal says "Deleting Workspace?" - Black text on status-colored tabs for readability. - Configurable diff command in Settings. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 91c47e0 commit 5672f2b

4 files changed

Lines changed: 29 additions & 11 deletions

File tree

Sources/NeetlyApp/PaneViewController.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,15 @@ class PaneViewController: NSViewController {
204204
tabs.compactMap { $0.viewController as? BrowserTabViewController }
205205
}
206206

207+
/// Send SIGINT to all terminal processes in this pane.
208+
func interruptAllTerminals() {
209+
for tab in tabs {
210+
if let terminal = tab.viewController as? TerminalTabViewController {
211+
terminal.interruptProcess()
212+
}
213+
}
214+
}
215+
207216
/// Returns info about all tabs in this pane for the tabs.list command.
208217
func listTabs() -> [TabListEntry] {
209218
return tabs.enumerated().map { (i, tab) in

Sources/NeetlyApp/SetupView.swift

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -619,7 +619,7 @@ struct WorkspaceListScreen: View {
619619
Button(role: .destructive, action: {
620620
workspaceToDelete = entry.name
621621
}) {
622-
Label("Delete", systemImage: "trash")
622+
Label("Delete Workspace", systemImage: "trash")
623623
}
624624
} label: {
625625
Image(systemName: "ellipsis")
@@ -698,7 +698,7 @@ struct DeleteWorktreeSheet: View {
698698

699699
var body: some View {
700700
VStack(alignment: .leading, spacing: 16) {
701-
Text("Delete worktree?")
701+
Text("Deleting Workspace?")
702702
.font(.system(size: 20, weight: .semibold))
703703

704704
VStack(alignment: .leading, spacing: 8) {
@@ -981,11 +981,8 @@ struct PRBadge: View {
981981
let prInfo: GitHubPRInfo
982982

983983
var body: some View {
984-
HStack(spacing: 5) {
985-
Circle()
986-
.fill(color)
987-
.frame(width: 6, height: 6)
988-
Text(verbatim: "\(stateLabel) #\(prInfo.number)")
984+
HStack(spacing: 0) {
985+
Text(verbatim: "PR #\(prInfo.number) (\(stateLabel))")
989986
.font(.system(size: 13, weight: .medium))
990987
.foregroundColor(color)
991988
}

Sources/NeetlyApp/TerminalTabViewController.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,14 @@ class TerminalTabViewController: NSViewController, LocalProcessTerminalViewDeleg
223223
terminalView.send(data: bytes[...])
224224
}
225225

226+
/// Send SIGINT to the shell process and its children (equivalent to Ctrl+C).
227+
func interruptProcess() {
228+
let pid = terminalView.process.shellPid
229+
guard pid > 0 else { return }
230+
// Send SIGINT to the process group so child processes (servers, etc.) also get it
231+
kill(-pid, SIGINT)
232+
}
233+
226234
// MARK: - LocalProcessTerminalViewDelegate
227235

228236
func processTerminated(source: TerminalView, exitCode: Int32?) {

Sources/NeetlyApp/WorkspaceWindowController.swift

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,10 @@ class Workspace {
100100
}
101101

102102
func stop() {
103+
// Send SIGINT to all terminal processes so they can clean up (release ports, etc.)
104+
for pane in splitTree.paneControllers.values {
105+
pane.interruptAllTerminals()
106+
}
103107
fileWatcher?.stop()
104108
socketServer.stop()
105109
}
@@ -337,26 +341,26 @@ private class WorkspaceTab: NSView {
337341
let repoY: CGFloat = contentBase + 20
338342
let wsY: CGFloat = contentBase + 4
339343

344+
let hasStatusColor = statusColor != nil
340345
let repoLabel = NSTextField(labelWithString: repoName)
341346
repoLabel.font = .systemFont(ofSize: 10)
342-
// Catppuccin Mocha: Subtext0 #a6adc8
343-
repoLabel.textColor = isActive ? NSColor(red: 0xa6/255, green: 0xad/255, blue: 0xc8/255, alpha: 1) : .secondaryLabelColor
347+
repoLabel.textColor = hasStatusColor ? .black.withAlphaComponent(0.6) : isActive ? NSColor(red: 0xa6/255, green: 0xad/255, blue: 0xc8/255, alpha: 1) : .secondaryLabelColor
344348
repoLabel.lineBreakMode = .byTruncatingTail
345349
repoLabel.frame = NSRect(x: 8, y: repoY, width: 140, height: 14)
346350
addSubview(repoLabel)
347351

348352
let wsLabel = NSTextField(labelWithString: workspaceName)
349353
wsLabel.font = .systemFont(ofSize: 14, weight: isActive ? .semibold : .regular)
350354
// Catppuccin Mocha: Text #cdd6f4
351-
wsLabel.textColor = isActive ? NSColor(red: 0xcd/255, green: 0xd6/255, blue: 0xf4/255, alpha: 1) : .labelColor
355+
wsLabel.textColor = hasStatusColor ? .black : isActive ? NSColor(red: 0xcd/255, green: 0xd6/255, blue: 0xf4/255, alpha: 1) : .labelColor
352356
wsLabel.lineBreakMode = .byTruncatingTail
353357
wsLabel.frame = NSRect(x: 8, y: wsY, width: 140, height: 17)
354358
addSubview(wsLabel)
355359

356360
closeBtn.image = NSImage(systemSymbolName: "xmark", accessibilityDescription: "Detach workspace")
357361
closeBtn.imagePosition = .imageOnly
358362
closeBtn.isBordered = false
359-
closeBtn.contentTintColor = isActive ? NSColor(red: 0xa6/255, green: 0xad/255, blue: 0xc8/255, alpha: 1) : .secondaryLabelColor
363+
closeBtn.contentTintColor = hasStatusColor ? .black.withAlphaComponent(0.6) : isActive ? NSColor(red: 0xa6/255, green: 0xad/255, blue: 0xc8/255, alpha: 1) : .secondaryLabelColor
360364
closeBtn.target = self
361365
closeBtn.action = #selector(closeClicked)
362366
closeBtn.imageScaling = .scaleProportionallyDown

0 commit comments

Comments
 (0)