Fixes terminal launch issues and adds tests for queries

This commit is contained in:
Mikayla Maki 2022-07-12 17:45:11 -07:00
parent 0bcd209a3f
commit 41e83b6be2
4 changed files with 235 additions and 119 deletions

View file

@ -615,9 +615,10 @@ impl TestAppContext {
}) })
} }
pub fn set_condition_duration(&mut self, duration: Duration) { pub fn set_condition_duration(&mut self, duration: Option<Duration>) {
self.condition_duration = Some(duration); self.condition_duration = duration;
} }
pub fn condition_duration(&self) -> Duration { pub fn condition_duration(&self) -> Duration {
self.condition_duration.unwrap_or_else(|| { self.condition_duration.unwrap_or_else(|| {
if std::env::var("CI").is_ok() { if std::env::var("CI").is_ok() {

View file

@ -27,4 +27,6 @@ dirs = "4.0.0"
gpui = { path = "../gpui", features = ["test-support"] } gpui = { path = "../gpui", features = ["test-support"] }
client = { path = "../client", features = ["test-support"]} client = { path = "../client", features = ["test-support"]}
project = { path = "../project", features = ["test-support"]} project = { path = "../project", features = ["test-support"]}
workspace = { path = "../workspace", features = ["test-support"] }

View file

@ -1,7 +1,7 @@
use gpui::{ModelHandle, ViewContext}; use gpui::{ModelHandle, ViewContext};
use workspace::Workspace; use workspace::Workspace;
use crate::{get_working_directory, DeployModal, Event, Terminal, TerminalConnection}; use crate::{get_wd_for_workspace, DeployModal, Event, Terminal, TerminalConnection};
struct StoredConnection(ModelHandle<TerminalConnection>); struct StoredConnection(ModelHandle<TerminalConnection>);
@ -20,14 +20,8 @@ pub fn deploy_modal(workspace: &mut Workspace, _: &DeployModal, cx: &mut ViewCon
} else { } else {
// No connection was stored, create a new terminal // No connection was stored, create a new terminal
if let Some(closed_terminal_handle) = workspace.toggle_modal(cx, |workspace, cx| { if let Some(closed_terminal_handle) = workspace.toggle_modal(cx, |workspace, cx| {
let project = workspace.project().read(cx); let wd = get_wd_for_workspace(workspace, cx);
let abs_path = project let this = cx.add_view(|cx| Terminal::new(wd, true, cx));
.active_entry()
.and_then(|entry_id| project.worktree_for_entry(entry_id, cx))
.and_then(|worktree_handle| worktree_handle.read(cx).as_local())
.and_then(get_working_directory);
let this = cx.add_view(|cx| Terminal::new(abs_path, true, cx));
let connection_handle = this.read(cx).connection.clone(); let connection_handle = this.read(cx).connection.clone();
cx.subscribe(&connection_handle, on_event).detach(); cx.subscribe(&connection_handle, on_event).detach();
this this

View file

@ -15,12 +15,12 @@ use dirs::home_dir;
use editor::Input; use editor::Input;
use futures::channel::mpsc::UnboundedSender; use futures::channel::mpsc::UnboundedSender;
use gpui::{ use gpui::{
actions, elements::*, impl_internal_actions, ClipboardItem, Entity, ModelHandle, actions, elements::*, impl_internal_actions, AppContext, ClipboardItem, Entity, ModelHandle,
MutableAppContext, View, ViewContext, MutableAppContext, View, ViewContext,
}; };
use modal::deploy_modal; use modal::deploy_modal;
use project::{LocalWorktree, Project, ProjectPath}; use project::{Project, ProjectPath};
use settings::Settings; use settings::Settings;
use smallvec::SmallVec; use smallvec::SmallVec;
use std::path::PathBuf; use std::path::PathBuf;
@ -43,6 +43,9 @@ const DEBUG_TERMINAL_HEIGHT: f32 = 200.;
const DEBUG_CELL_WIDTH: f32 = 5.; const DEBUG_CELL_WIDTH: f32 = 5.;
const DEBUG_LINE_HEIGHT: f32 = 5.; const DEBUG_LINE_HEIGHT: f32 = 5.;
//For bel, use a yellow dot. (equivalent to dirty file with conflict)
//For title, introduce max title length and
///Event to transmit the scroll from the element to the view ///Event to transmit the scroll from the element to the view
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct ScrollTerminal(pub i32); pub struct ScrollTerminal(pub i32);
@ -115,6 +118,7 @@ impl Entity for Terminal {
impl Terminal { impl Terminal {
///Create a new Terminal view. This spawns a task, a thread, and opens the TTY devices ///Create a new Terminal view. This spawns a task, a thread, and opens the TTY devices
///To get the right working directory from a workspace, use: `get_wd_for_workspace()`
fn new(working_directory: Option<PathBuf>, modal: bool, cx: &mut ViewContext<Self>) -> Self { fn new(working_directory: Option<PathBuf>, modal: bool, cx: &mut ViewContext<Self>) -> Self {
//The details here don't matter, the terminal will be resized on the first layout //The details here don't matter, the terminal will be resized on the first layout
let size_info = SizeInfo::new( let size_info = SizeInfo::new(
@ -194,18 +198,8 @@ impl Terminal {
///Create a new Terminal in the current working directory or the user's home directory ///Create a new Terminal in the current working directory or the user's home directory
fn deploy(workspace: &mut Workspace, _: &Deploy, cx: &mut ViewContext<Workspace>) { fn deploy(workspace: &mut Workspace, _: &Deploy, cx: &mut ViewContext<Workspace>) {
let project = workspace.project().read(cx); let wd = get_wd_for_workspace(workspace, cx);
workspace.add_item(Box::new(cx.add_view(|cx| Terminal::new(wd, false, cx))), cx);
let abs_path = project
.active_entry()
.and_then(|entry_id| project.worktree_for_entry(entry_id, cx))
.and_then(|worktree_handle| worktree_handle.read(cx).as_local())
.and_then(get_working_directory);
workspace.add_item(
Box::new(cx.add_view(|cx| Terminal::new(abs_path, false, cx))),
cx,
);
} }
///Tell Zed to close us ///Tell Zed to close us
@ -432,10 +426,28 @@ impl Item for Terminal {
} }
} }
fn get_working_directory(wt: &LocalWorktree) -> Option<PathBuf> { fn get_wd_for_workspace(workspace: &Workspace, cx: &AppContext) -> Option<PathBuf> {
Some(wt.abs_path().to_path_buf()) let project = workspace.project().read(cx);
.filter(|path| path.is_dir())
.or_else(|| home_dir()) project
.active_entry()
.and_then(|entry_id| project.worktree_for_entry(entry_id, cx))
.or_else(|| workspace.worktrees(cx).next())
.and_then(|worktree_handle| worktree_handle.read(cx).as_local())
.and_then(|wt| {
println!("{:?}", wt.root_entry());
let rs = wt
.root_entry()
.filter(|re| re.is_dir())
.map(|_| wt.abs_path().to_path_buf());
println!("{:?}", rs);
rs
})
.or_else(|| {
println!("HERE");
home_dir()
})
} }
#[cfg(test)] #[cfg(test)]
@ -450,19 +462,16 @@ mod tests {
}; };
use gpui::TestAppContext; use gpui::TestAppContext;
use itertools::Itertools; use itertools::Itertools;
use project::{FakeFs, Fs, RealFs, RemoveOptions, Worktree};
use std::{ use std::{path::Path, time::Duration};
path::Path, use workspace::AppState;
sync::{atomic::AtomicUsize, Arc},
time::Duration,
};
///Basic integration test, can we get the terminal to show up, execute a command, ///Basic integration test, can we get the terminal to show up, execute a command,
//and produce noticable output? //and produce noticable output?
#[gpui::test] #[gpui::test]
async fn test_terminal(cx: &mut TestAppContext) { async fn test_terminal(cx: &mut TestAppContext) {
let terminal = cx.add_view(Default::default(), |cx| Terminal::new(None, false, cx)); let terminal = cx.add_view(Default::default(), |cx| Terminal::new(None, false, cx));
cx.set_condition_duration(Duration::from_secs(2));
terminal.update(cx, |terminal, cx| { terminal.update(cx, |terminal, cx| {
terminal.connection.update(cx, |connection, cx| { terminal.connection.update(cx, |connection, cx| {
connection.write_to_pty("expr 3 + 4".to_string(), cx); connection.write_to_pty("expr 3 + 4".to_string(), cx);
@ -470,6 +479,7 @@ mod tests {
terminal.carriage_return(&Return, cx); terminal.carriage_return(&Return, cx);
}); });
cx.set_condition_duration(Some(Duration::from_secs(2)));
terminal terminal
.condition(cx, |terminal, cx| { .condition(cx, |terminal, cx| {
let term = terminal.connection.read(cx).term.clone(); let term = terminal.connection.read(cx).term.clone();
@ -477,95 +487,15 @@ mod tests {
content.contains("7") content.contains("7")
}) })
.await; .await;
cx.set_condition_duration(None);
} }
#[gpui::test] /// Integration test for selections, clipboard, and terminal execution
async fn single_file_worktree(cx: &mut TestAppContext) {
let mut async_cx = cx.to_async();
let http_client = client::test::FakeHttpClient::with_404_response();
let client = client::Client::new(http_client.clone());
let fake_fs = FakeFs::new(cx.background().clone());
let path = Path::new("/file/");
fake_fs.insert_file(path, "a".to_string()).await;
let worktree_handle = Worktree::local(
client,
path,
true,
fake_fs,
Arc::new(AtomicUsize::new(0)),
&mut async_cx,
)
.await
.ok()
.unwrap();
async_cx.update(|cx| {
let wt = worktree_handle.read(cx).as_local().unwrap();
let wd = get_working_directory(wt);
assert!(wd.is_some());
let path = wd.unwrap();
//This should be the system's working directory, so querying the real file system is probably ok.
assert!(path.is_dir());
assert_eq!(path, home_dir().unwrap());
});
}
#[gpui::test]
async fn test_worktree_directory(cx: &mut TestAppContext) {
let mut async_cx = cx.to_async();
let http_client = client::test::FakeHttpClient::with_404_response();
let client = client::Client::new(http_client.clone());
let fs = RealFs;
let mut test_wd = home_dir().unwrap();
test_wd.push("dir");
fs.create_dir(test_wd.as_path())
.await
.expect("File could not be created");
let worktree_handle = Worktree::local(
client,
test_wd.clone(),
true,
Arc::new(RealFs),
Arc::new(AtomicUsize::new(0)),
&mut async_cx,
)
.await
.ok()
.unwrap();
async_cx.update(|cx| {
let wt = worktree_handle.read(cx).as_local().unwrap();
let wd = get_working_directory(wt);
assert!(wd.is_some());
let path = wd.unwrap();
assert!(path.is_dir());
assert_eq!(path, test_wd);
});
//Clean up after ourselves.
fs.remove_dir(
test_wd.as_path(),
RemoveOptions {
recursive: false,
ignore_if_not_exists: true,
},
)
.await
.ok()
.expect("Could not remove test directory");
}
///If this test is failing for you, check that DEBUG_TERMINAL_WIDTH is wide enough to fit your entire command prompt!
#[gpui::test] #[gpui::test]
async fn test_copy(cx: &mut TestAppContext) { async fn test_copy(cx: &mut TestAppContext) {
let mut result_line: i32 = 0; let mut result_line: i32 = 0;
let terminal = cx.add_view(Default::default(), |cx| Terminal::new(None, false, cx)); let terminal = cx.add_view(Default::default(), |cx| Terminal::new(None, false, cx));
cx.set_condition_duration(Duration::from_secs(2)); cx.set_condition_duration(Some(Duration::from_secs(2)));
terminal.update(cx, |terminal, cx| { terminal.update(cx, |terminal, cx| {
terminal.connection.update(cx, |connection, cx| { terminal.connection.update(cx, |connection, cx| {
@ -601,6 +531,195 @@ mod tests {
}); });
cx.assert_clipboard_content(Some(&"7")); cx.assert_clipboard_content(Some(&"7"));
cx.set_condition_duration(None);
}
///Working directory calculation tests
///No Worktrees in project -> home_dir()
#[gpui::test]
async fn no_worktree(cx: &mut TestAppContext) {
//Setup variables
let params = cx.update(AppState::test);
let project = Project::test(params.fs.clone(), [], cx).await;
let (_, workspace) = cx.add_window(|cx| Workspace::new(project.clone(), cx));
//Test
cx.read(|cx| {
let workspace = workspace.read(cx);
let active_entry = project.read(cx).active_entry();
//Make sure enviroment is as expeted
assert!(active_entry.is_none());
assert!(workspace.worktrees(cx).next().is_none());
let res = get_wd_for_workspace(workspace, cx);
assert_eq!(res, home_dir())
});
}
///No active entry, but a worktree, worktree is a file -> home_dir()
#[gpui::test]
async fn no_active_entry_worktree_is_file(cx: &mut TestAppContext) {
//Setup variables
let params = cx.update(AppState::test);
let project = Project::test(params.fs.clone(), [], cx).await;
let (_, workspace) = cx.add_window(|cx| Workspace::new(project.clone(), cx));
let (wt, _) = project
.update(cx, |project, cx| {
project.find_or_create_local_worktree("/root.txt", true, cx)
})
.await
.unwrap();
cx.update(|cx| {
wt.update(cx, |wt, cx| {
wt.as_local()
.unwrap()
.create_entry(Path::new(""), false, cx)
})
})
.await
.unwrap();
//Test
cx.read(|cx| {
let workspace = workspace.read(cx);
let active_entry = project.read(cx).active_entry();
//Make sure enviroment is as expeted
assert!(active_entry.is_none());
assert!(workspace.worktrees(cx).next().is_some());
let res = get_wd_for_workspace(workspace, cx);
assert_eq!(res, home_dir())
});
}
//No active entry, but a worktree, worktree is a folder -> worktree_folder
#[gpui::test]
async fn no_active_entry_worktree_is_dir(cx: &mut TestAppContext) {
//Setup variables
let params = cx.update(AppState::test);
let project = Project::test(params.fs.clone(), [], cx).await;
let (_, workspace) = cx.add_window(|cx| Workspace::new(project.clone(), cx));
let (wt, _) = project
.update(cx, |project, cx| {
project.find_or_create_local_worktree("/root/", true, cx)
})
.await
.unwrap();
//Setup root folder
cx.update(|cx| {
wt.update(cx, |wt, cx| {
wt.as_local().unwrap().create_entry(Path::new(""), true, cx)
})
})
.await
.unwrap();
//Test
cx.update(|cx| {
let workspace = workspace.read(cx);
let active_entry = project.read(cx).active_entry();
assert!(active_entry.is_none());
assert!(workspace.worktrees(cx).next().is_some());
let res = get_wd_for_workspace(workspace, cx);
assert_eq!(res, Some((Path::new("/root/")).to_path_buf()));
});
}
//Active entry with a work tree, worktree is a file -> home_dir()
#[gpui::test]
async fn active_entry_worktree_is_file(cx: &mut TestAppContext) {
//Setup variables
let params = cx.update(AppState::test);
let project = Project::test(params.fs.clone(), [], cx).await;
let (_, workspace) = cx.add_window(|cx| Workspace::new(project.clone(), cx));
let (wt, _) = project
.update(cx, |project, cx| {
project.find_or_create_local_worktree("/root.txt", true, cx)
})
.await
.unwrap();
//Setup root
let entry = cx
.update(|cx| {
wt.update(cx, |wt, cx| {
wt.as_local()
.unwrap()
.create_entry(Path::new(""), false, cx)
})
})
.await
.unwrap();
cx.update(|cx| {
let p = ProjectPath {
worktree_id: wt.read(cx).id(),
path: entry.path,
};
project.update(cx, |project, cx| project.set_active_path(Some(p), cx));
});
//Test
cx.update(|cx| {
let workspace = workspace.read(cx);
let active_entry = project.read(cx).active_entry();
assert!(active_entry.is_some());
let res = get_wd_for_workspace(workspace, cx);
assert_eq!(res, home_dir());
});
}
//Active entry, with a worktree, worktree is a folder -> worktree_folder
#[gpui::test]
async fn active_entry_worktree_is_dir(cx: &mut TestAppContext) {
//Setup variables
let params = cx.update(AppState::test);
let project = Project::test(params.fs.clone(), [], cx).await;
let (_, workspace) = cx.add_window(|cx| Workspace::new(project.clone(), cx));
let (wt, _) = project
.update(cx, |project, cx| {
project.find_or_create_local_worktree("/root/", true, cx)
})
.await
.unwrap();
//Setup root
let entry = cx
.update(|cx| {
wt.update(cx, |wt, cx| {
wt.as_local().unwrap().create_entry(Path::new(""), true, cx)
})
})
.await
.unwrap();
cx.update(|cx| {
let p = ProjectPath {
worktree_id: wt.read(cx).id(),
path: entry.path,
};
project.update(cx, |project, cx| project.set_active_path(Some(p), cx));
});
//Test
cx.update(|cx| {
let workspace = workspace.read(cx);
let active_entry = project.read(cx).active_entry();
assert!(active_entry.is_some());
let res = get_wd_for_workspace(workspace, cx);
assert_eq!(res, Some((Path::new("/root/")).to_path_buf()));
});
} }
pub(crate) fn grid_as_str(grid_iterator: GridIterator<Cell>) -> String { pub(crate) fn grid_as_str(grid_iterator: GridIterator<Cell>) -> String {