diff --git a/assets/settings/default.json b/assets/settings/default.json index bd41f1704f6be57921202a71fe229729317e204f..28e7a45248510a94c10a7669879a1e5a7ab8a39b 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -1596,15 +1596,18 @@ // Default height when the terminal is docked to the bottom. "default_height": 320, // What working directory to use when launching the terminal. - // May take 4 values: - // 1. Use the current file's project directory. Fallback to the + // May take 5 values: + // 1. Use the current file's directory, falling back to the project + // directory, then the first project in the workspace. + // "working_directory": "current_file_directory" + // 2. Use the current file's project directory. Fallback to the // first project directory strategy if unsuccessful // "working_directory": "current_project_directory" - // 2. Use the first project in this workspace's directory + // 3. Use the first project in this workspace's directory // "working_directory": "first_project_directory" - // 3. Always use this platform's home directory (if we can find it) + // 4. Always use this platform's home directory (if we can find it) // "working_directory": "always_home" - // 4. Always use a specific directory. This value will be shell expanded. + // 5. Always use a specific directory. This value will be shell expanded. // If this path is not a valid directory the terminal will default to // this platform's home directory (if we can find it) // "working_directory": { diff --git a/crates/project/src/terminals.rs b/crates/project/src/terminals.rs index 5ab3c4c36214d744aff7d24f3a3877ee91b1e7d4..475ba2ce0f58de4d785613a0ef6d915fe2ea408c 100644 --- a/crates/project/src/terminals.rs +++ b/crates/project/src/terminals.rs @@ -26,6 +26,20 @@ pub struct Terminals { } impl Project { + pub fn active_entry_directory(&self, cx: &App) -> Option { + let entry_id = self.active_entry()?; + let worktree = self.worktree_for_entry(entry_id, cx)?; + let worktree = worktree.read(cx); + let entry = worktree.entry_for_id(entry_id)?; + + let absolute_path = worktree.absolutize(entry.path.as_ref()); + if entry.is_dir() { + Some(absolute_path) + } else { + absolute_path.parent().map(|p| p.to_path_buf()) + } + } + pub fn active_project_directory(&self, cx: &App) -> Option> { self.active_entry() .and_then(|entry_id| self.worktree_for_entry(entry_id, cx)) diff --git a/crates/settings_content/src/terminal.rs b/crates/settings_content/src/terminal.rs index e8a30f65bc541a8ee129cc74a9c51d6c6098abcd..a13613badfaa0a375dbcbdf6424e7bda59a84dc4 100644 --- a/crates/settings_content/src/terminal.rs +++ b/crates/settings_content/src/terminal.rs @@ -219,6 +219,9 @@ pub enum Shell { #[strum_discriminants(derive(strum::VariantArray, strum::VariantNames, strum::FromRepr))] #[serde(rename_all = "snake_case")] pub enum WorkingDirectory { + /// Use the current file's directory, falling back to the project directory, + /// then the first project in the workspace. + CurrentFileDirectory, /// Use the current file's project directory. Fallback to the /// first project directory strategy if unsuccessful. CurrentProjectDirectory, diff --git a/crates/settings_ui/src/page_data.rs b/crates/settings_ui/src/page_data.rs index 7d60b7e6a88c6d4a813d9b51cb6bfae5611ab2af..a503eb8dd11d2ba6ef2695cdbef90388a791cf86 100644 --- a/crates/settings_ui/src/page_data.rs +++ b/crates/settings_ui/src/page_data.rs @@ -5762,6 +5762,9 @@ fn terminal_page() -> SettingsPage { .working_directory .get_or_insert_with(|| settings::WorkingDirectory::CurrentProjectDirectory); *settings_value = match value { + settings::WorkingDirectoryDiscriminants::CurrentFileDirectory => { + settings::WorkingDirectory::CurrentFileDirectory + }, settings::WorkingDirectoryDiscriminants::CurrentProjectDirectory => { settings::WorkingDirectory::CurrentProjectDirectory } @@ -5797,6 +5800,7 @@ fn terminal_page() -> SettingsPage { fields: dynamic_variants::() .into_iter() .map(|variant| match variant { + settings::WorkingDirectoryDiscriminants::CurrentFileDirectory => vec![], settings::WorkingDirectoryDiscriminants::CurrentProjectDirectory => vec![], settings::WorkingDirectoryDiscriminants::FirstProjectDirectory => vec![], settings::WorkingDirectoryDiscriminants::AlwaysHome => vec![], diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index 42dc102d1ccc01b1df6f3df316503447a38fd79d..cdefa03e33db34b1805b12c8c9696e8d5b3fe123 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -1785,13 +1785,12 @@ impl SearchableItem for TerminalView { /// Falls back to home directory when no project directory is available. pub(crate) fn default_working_directory(workspace: &Workspace, cx: &App) -> Option { let directory = match &TerminalSettings::get_global(cx).working_directory { - WorkingDirectory::CurrentProjectDirectory => workspace + WorkingDirectory::CurrentFileDirectory => workspace .project() .read(cx) - .active_project_directory(cx) - .as_deref() - .map(Path::to_path_buf) - .or_else(|| first_project_directory(workspace, cx)), + .active_entry_directory(cx) + .or_else(|| current_project_directory(workspace, cx)), + WorkingDirectory::CurrentProjectDirectory => current_project_directory(workspace, cx), WorkingDirectory::FirstProjectDirectory => first_project_directory(workspace, cx), WorkingDirectory::AlwaysHome => None, WorkingDirectory::Always { directory } => shellexpand::full(directory) @@ -1801,6 +1800,17 @@ pub(crate) fn default_working_directory(workspace: &Workspace, cx: &App) -> Opti }; directory.or_else(dirs::home_dir) } + +fn current_project_directory(workspace: &Workspace, cx: &App) -> Option { + workspace + .project() + .read(cx) + .active_project_directory(cx) + .as_deref() + .map(Path::to_path_buf) + .or_else(|| first_project_directory(workspace, cx)) +} + ///Gets the first project's home directory, or the home directory fn first_project_directory(workspace: &Workspace, cx: &App) -> Option { let worktree = workspace.worktrees(cx).next()?.read(cx); @@ -1819,6 +1829,7 @@ mod tests { use gpui::TestAppContext; use project::{Entry, Project, ProjectPath, Worktree}; use std::path::Path; + use util::paths::PathStyle; use util::rel_path::RelPath; use workspace::AppState; @@ -1928,6 +1939,67 @@ mod tests { }); } + // active_entry_directory: No active entry -> returns None (used by CurrentFileDirectory) + #[gpui::test] + async fn active_entry_directory_no_active_entry(cx: &mut TestAppContext) { + let (project, _workspace) = init_test(cx).await; + + let (_wt, _entry) = create_folder_wt(project.clone(), "/root/", cx).await; + + cx.update(|cx| { + assert!(project.read(cx).active_entry().is_none()); + + let res = project.read(cx).active_entry_directory(cx); + assert_eq!(res, None); + }); + } + + // active_entry_directory: Active entry is file -> returns parent directory (used by CurrentFileDirectory) + #[gpui::test] + async fn active_entry_directory_active_file(cx: &mut TestAppContext) { + let (project, _workspace) = init_test(cx).await; + + let (wt, _entry) = create_folder_wt(project.clone(), "/root/", cx).await; + let entry = cx + .update(|cx| { + wt.update(cx, |wt, cx| { + wt.create_entry( + RelPath::new(Path::new("src/main.rs"), PathStyle::local()) + .unwrap() + .as_ref() + .into(), + false, + None, + cx, + ) + }) + }) + .await + .unwrap() + .into_included() + .unwrap(); + insert_active_entry_for(wt, entry, project.clone(), cx); + + cx.update(|cx| { + let res = project.read(cx).active_entry_directory(cx); + assert_eq!(res, Some(Path::new("/root/src").to_path_buf())); + }); + } + + // active_entry_directory: Active entry is directory -> returns that directory (used by CurrentFileDirectory) + #[gpui::test] + async fn active_entry_directory_active_dir(cx: &mut TestAppContext) { + let (project, _workspace) = init_test(cx).await; + + let (wt, entry) = create_folder_wt(project.clone(), "/root/", cx).await; + insert_active_entry_for(wt, entry, project.clone(), cx); + + cx.update(|cx| { + let res = project.read(cx).active_entry_directory(cx); + assert_eq!(res, Some(Path::new("/root/").to_path_buf())); + }); + } + /// Creates a worktree with 1 file: /root.txt pub async fn init_test(cx: &mut TestAppContext) -> (Entity, Entity) { let params = cx.update(AppState::test); diff --git a/docs/src/reference/all-settings.md b/docs/src/reference/all-settings.md index 16e482d4f98c6cd400b1e19ce560f2768a125fe6..b8d855fdb6860b8ce5b60cb180b5227d631aa5c4 100644 --- a/docs/src/reference/all-settings.md +++ b/docs/src/reference/all-settings.md @@ -4112,7 +4112,17 @@ Example command to set the title: `echo -e "\e]2;New Title\007";` **Options** -1. Use the current file's project directory. Fallback to the first project directory strategy if unsuccessful. +1. Use the current file's directory, falling back to the project directory, then the first project in the workspace. + +```json [settings] +{ + "terminal": { + "working_directory": "current_file_directory" + } +} +``` + +2. Use the current file's project directory. Fallback to the first project directory strategy if unsuccessful. ```json [settings] { @@ -4122,7 +4132,7 @@ Example command to set the title: `echo -e "\e]2;New Title\007";` } ``` -2. Use the first project in this workspace's directory. Fallback to using this platform's home directory. +3. Use the first project in this workspace's directory. Fallback to using this platform's home directory. ```json [settings] { @@ -4132,7 +4142,7 @@ Example command to set the title: `echo -e "\e]2;New Title\007";` } ``` -3. Always use this platform's home directory if it can be found. +4. Always use this platform's home directory if it can be found. ```json [settings] { @@ -4142,7 +4152,7 @@ Example command to set the title: `echo -e "\e]2;New Title\007";` } ``` -4. Always use a specific directory. This value will be shell expanded. If this path is not a valid directory the terminal will default to this platform's home directory. +5. Always use a specific directory. This value will be shell expanded. If this path is not a valid directory the terminal will default to this platform's home directory. ```json [settings] { diff --git a/docs/src/terminal.md b/docs/src/terminal.md index 8232d9dcb2d3fc7da9ca4847171f8978de8cf92d..6f6f7aac88c8b3149f4c66677fa5717156366d25 100644 --- a/docs/src/terminal.md +++ b/docs/src/terminal.md @@ -58,12 +58,13 @@ To pass arguments to your shell: Control where new terminals start: -| Value | Behavior | -| --------------------------------------------- | --------------------------------------------------------------- | -| `"current_project_directory"` | Uses the project directory of the currently open file (default) | -| `"first_project_directory"` | Uses the first project in your workspace | -| `"always_home"` | Always starts in your home directory | -| `{ "always": { "directory": "~/projects" } }` | Always starts in a specific directory | +| Value | Behavior | +| --------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | +| `"current_file_directory"` | Uses the current file's directory, falling back to the project directory, then the first project in the workspace | +| `"current_project_directory"` | Uses the current file's project directory (default) | +| `"first_project_directory"` | Uses the first project in your workspace | +| `"always_home"` | Always starts in your home directory | +| `{ "always": { "directory": "~/projects" } }` | Always starts in a specific directory | ```json [settings] {