Repository navigation
Add file tree sidebar with cmd-click path composition #738
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鈥檒l occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1225,6 +1225,8 @@ struct ContentView: View { | |
| @EnvironmentObject var notificationStore: TerminalNotificationStore | ||
| @EnvironmentObject var sidebarState: SidebarState | ||
| @EnvironmentObject var sidebarSelectionState: SidebarSelectionState | ||
| @AppStorage("sidebarContentMode") private var sidebarContentMode = SidebarContentMode.tabs.rawValue | ||
| @StateObject private var fileTreeModel = FileTreeModel() | ||
| @State private var sidebarWidth: CGFloat = 200 | ||
| @State private var hoveredResizerHandles: Set<SidebarResizerHandle> = [] | ||
| @State private var isResizerDragging = false | ||
|
|
@@ -1773,14 +1775,57 @@ struct ContentView: View { | |
| } | ||
| } | ||
|
|
||
| private var sidebarView: some View { | ||
| private var tabsSidebarContent: some View { | ||
| VerticalTabsSidebar( | ||
| updateViewModel: updateViewModel, | ||
| selection: $sidebarSelectionState.selection, | ||
| selectedTabIds: $selectedTabIds, | ||
| lastSidebarSelectionIndex: $lastSidebarSelectionIndex | ||
| ) | ||
| } | ||
|
|
||
| private var fileTreeSidebarContent: some View { | ||
| Group { | ||
| if let workspace = tabManager.selectedTab { | ||
| FileTreeSidebar( | ||
| model: fileTreeModel, | ||
| workspace: workspace, | ||
| onComposePath: { path in | ||
| sendPathToFocusedTerminal(path) | ||
| } | ||
| ) | ||
| } else { | ||
| Text("No workspace") | ||
| .font(.system(size: 12)) | ||
| .foregroundColor(.secondary) | ||
| .frame(maxWidth: .infinity, maxHeight: .infinity) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private var sidebarView: some View { | ||
| VStack(spacing: 0) { | ||
| if sidebarContentMode == SidebarContentMode.fileTree.rawValue { | ||
| fileTreeSidebarContent | ||
| } else { | ||
| tabsSidebarContent | ||
| } | ||
| } | ||
| .frame(width: sidebarWidth) | ||
| .background(SidebarBackdrop().ignoresSafeArea()) | ||
| } | ||
|
|
||
| private func sendPathToFocusedTerminal(_ path: String) { | ||
| guard let workspace = tabManager.selectedTab, | ||
| let terminalPanel = workspace.focusedTerminalPanel else { return } | ||
| // Shell-escape the path so spaces and special chars don't break commands | ||
| let escaped = shellEscapePath(path) | ||
| terminalPanel.sendText(escaped) | ||
| } | ||
|
Comment on lines
+1818
to
+1824
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 馃З Analysis chain馃弫 Script executed: #!/bin/bash
# Verify delimiter logic in onComposePath call sites and sendPathToFocusedTerminal usage
rg -n -C5 'onComposePath|sendPathToFocusedTerminal|shellEscapePath' Sources --type swift | head -200Repository: manaflow-ai/cmux Length of output: 7141 馃弫 Script executed: #!/bin/bash
# Find terminalPanel.sendText implementation and other call sites
rg -n 'func sendText|\.sendText\(' Sources --type swift -A2 | head -100Repository: manaflow-ai/cmux Length of output: 3789 馃弫 Script executed: #!/bin/bash
# Read the complete sendText implementation in GhosttyTerminalView
sed -n '2100,2120p' Sources/GhosttyTerminalView.swiftRepository: manaflow-ai/cmux Length of output: 748 Add trailing space to When users cmd-click multiple file paths sequentially, each call to private func sendPathToFocusedTerminal(_ path: String) {
guard let workspace = tabManager.selectedTab,
let terminalPanel = workspace.focusedTerminalPanel else { return }
// Shell-escape the path so spaces and special chars don't break commands
let escaped = shellEscapePath(path)
terminalPanel.sendText(escaped + " ")
}馃 Prompt for AI Agents |
||
|
|
||
| private func shellEscapePath(_ path: String) -> String { | ||
| let escaped = path.replacingOccurrences(of: "'", with: "'\\''") | ||
| return "'\(escaped)'" | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
Comment on lines
+1826
to
1829
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Inconsistent with existing shell escaping in Extract |
||
|
|
||
| /// Space at top of content area for the titlebar. This must be at least the actual titlebar | ||
|
|
@@ -1868,6 +1913,17 @@ struct ContentView: View { | |
| notificationStore: TerminalNotificationStore.shared, | ||
| viewModel: fullscreenControlsViewModel, | ||
| onToggleSidebar: { sidebarState.toggle() }, | ||
| onToggleFileTree: { | ||
| let current = UserDefaults.standard.string(forKey: "sidebarContentMode") ?? SidebarContentMode.tabs.rawValue | ||
| if current == SidebarContentMode.fileTree.rawValue { | ||
| UserDefaults.standard.set(SidebarContentMode.tabs.rawValue, forKey: "sidebarContentMode") | ||
| } else { | ||
| UserDefaults.standard.set(SidebarContentMode.fileTree.rawValue, forKey: "sidebarContentMode") | ||
| if !sidebarState.isVisible { | ||
| sidebarState.toggle() | ||
| } | ||
| } | ||
| }, | ||
| onToggleNotifications: { [fullscreenControlsViewModel] in | ||
| AppDelegate.shared?.toggleNotificationsPopover( | ||
| animated: true, | ||
|
|
@@ -2493,7 +2549,7 @@ struct ContentView: View { | |
| windowId: windowId, | ||
| tabManager: tabManager, | ||
| sidebarState: sidebarState, | ||
| sidebarSelectionState: sidebarSelectionState | ||
| sidebarSelectionState: sidebarSelectionState, | ||
| ) | ||
| installFileDropOverlay(on: window, tabManager: tabManager) | ||
| })) | ||
|
|
@@ -5665,7 +5721,6 @@ struct VerticalTabsSidebar: View { | |
| } | ||
| .accessibilityIdentifier("Sidebar") | ||
| .ignoresSafeArea() | ||
| .background(SidebarBackdrop().ignoresSafeArea()) | ||
| .background( | ||
| WindowAccessor { window in | ||
| commandKeyMonitor.setHostWindow(window) | ||
|
|
@@ -8374,7 +8429,7 @@ enum SidebarSelection { | |
| case notifications | ||
| } | ||
|
|
||
| private struct ClearScrollBackground: ViewModifier { | ||
| struct ClearScrollBackground: ViewModifier { | ||
| func body(content: Content) -> some View { | ||
| if #available(macOS 13.0, *) { | ||
| content | ||
|
|
||
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.
Incomplete shell escaping - missing glob chars (
*,?,[,]), brace expansion ({,}), command substitution (`), tilde (~), hash (#), backslash (\), and control characters. A file namedtest*.txtor$(dangerous)would be sent unquoted and cause shell expansion/execution.Safer approach: always wrap in single quotes: