Detailed changes
@@ -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": {
@@ -26,6 +26,20 @@ pub struct Terminals {
}
impl Project {
+ pub fn active_entry_directory(&self, cx: &App) -> Option<PathBuf> {
+ 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<Arc<Path>> {
self.active_entry()
.and_then(|entry_id| self.worktree_for_entry(entry_id, cx))
@@ -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,
@@ -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::<settings::WorkingDirectory>()
.into_iter()
.map(|variant| match variant {
+ settings::WorkingDirectoryDiscriminants::CurrentFileDirectory => vec![],
settings::WorkingDirectoryDiscriminants::CurrentProjectDirectory => vec![],
settings::WorkingDirectoryDiscriminants::FirstProjectDirectory => vec![],
settings::WorkingDirectoryDiscriminants::AlwaysHome => vec![],
@@ -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<PathBuf> {
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<PathBuf> {
+ 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<PathBuf> {
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<Project>, Entity<Workspace>) {
let params = cx.update(AppState::test);
@@ -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]
{
@@ -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]
{