mirror of
https://github.com/zed-industries/zed.git
synced 2024-12-27 09:02:26 +03:00
Add --add/--new to control CLI behaviour (#9202)
When neither is specified, if you open a directory you get a new workspace, otherwise files are added to your existing workspace. With --new files are always opened in a new workspace With --add directories are always added to an existing workspace Fixes #9076 Fixes #4861 Fixes #5370 Release Notes: - Added `-n/--new` and `-a/--add` to the zed CLI. When neither is specified, if you open a directory you get a new workspace, otherwise files are added to your existing workspace. With `--new` files are always opened in a new workspace, with `--add` directories are always added to an existing workspace. ([#9076](https://github.com/zed-industries/zed/issues/9096), [#4861](https://github.com/zed-industries/zed/issues/4861), [#5370](https://github.com/zed-industries/zed/issues/5370)).
This commit is contained in:
parent
89c67fb1ab
commit
05dfe96f0c
@ -9,12 +9,11 @@ pub struct IpcHandshake {
|
|||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub enum CliRequest {
|
pub enum CliRequest {
|
||||||
// The filed is named `path` for compatibility, but now CLI can request
|
Open {
|
||||||
// opening a path at a certain row and/or column: `some/path:123` and `some/path:123:456`.
|
paths: Vec<String>,
|
||||||
//
|
wait: bool,
|
||||||
// Since Zed CLI has to be installed separately, there can be situations when old CLI is
|
open_new_workspace: Option<bool>,
|
||||||
// querying new Zed editors, support both formats by using `String` here and parsing it on Zed side later.
|
},
|
||||||
Open { paths: Vec<String>, wait: bool },
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
@ -12,12 +12,18 @@ use std::{
|
|||||||
};
|
};
|
||||||
use util::paths::PathLikeWithPosition;
|
use util::paths::PathLikeWithPosition;
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser, Debug)]
|
||||||
#[clap(name = "zed", global_setting(clap::AppSettings::NoAutoVersion))]
|
#[clap(name = "zed", global_setting(clap::AppSettings::NoAutoVersion))]
|
||||||
struct Args {
|
struct Args {
|
||||||
/// Wait for all of the given paths to be opened/closed before exiting.
|
/// Wait for all of the given paths to be opened/closed before exiting.
|
||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
wait: bool,
|
wait: bool,
|
||||||
|
/// Add files to the currently open workspace
|
||||||
|
#[clap(short, long, overrides_with = "new")]
|
||||||
|
add: bool,
|
||||||
|
/// Create a new workspace
|
||||||
|
#[clap(short, long, overrides_with = "add")]
|
||||||
|
new: bool,
|
||||||
/// A sequence of space-separated paths that you want to open.
|
/// A sequence of space-separated paths that you want to open.
|
||||||
///
|
///
|
||||||
/// Use `path:line:row` syntax to open a file at a specific location.
|
/// Use `path:line:row` syntax to open a file at a specific location.
|
||||||
@ -67,6 +73,13 @@ fn main() -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let (tx, rx) = bundle.launch()?;
|
let (tx, rx) = bundle.launch()?;
|
||||||
|
let open_new_workspace = if args.new {
|
||||||
|
Some(true)
|
||||||
|
} else if args.add {
|
||||||
|
Some(false)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
tx.send(CliRequest::Open {
|
tx.send(CliRequest::Open {
|
||||||
paths: args
|
paths: args
|
||||||
@ -81,6 +94,7 @@ fn main() -> Result<()> {
|
|||||||
})
|
})
|
||||||
.collect::<Result<_>>()?,
|
.collect::<Result<_>>()?,
|
||||||
wait: args.wait,
|
wait: args.wait,
|
||||||
|
open_new_workspace,
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
while let Ok(response) = rx.recv() {
|
while let Ok(response) = rx.recv() {
|
||||||
|
@ -102,7 +102,14 @@ pub fn new_journal_entry(app_state: Arc<AppState>, cx: &mut WindowContext) {
|
|||||||
cx.spawn(|mut cx| async move {
|
cx.spawn(|mut cx| async move {
|
||||||
let (journal_dir, entry_path) = create_entry.await?;
|
let (journal_dir, entry_path) = create_entry.await?;
|
||||||
let (workspace, _) = cx
|
let (workspace, _) = cx
|
||||||
.update(|cx| workspace::open_paths(&[journal_dir], app_state, None, cx))?
|
.update(|cx| {
|
||||||
|
workspace::open_paths(
|
||||||
|
&[journal_dir],
|
||||||
|
app_state,
|
||||||
|
workspace::OpenOptions::default(),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})?
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let opened = workspace
|
let opened = workspace
|
||||||
|
@ -1145,18 +1145,24 @@ impl Project {
|
|||||||
.map(|worktree| worktree.read(cx).id())
|
.map(|worktree| worktree.read(cx).id())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn contains_paths(&self, paths: &[PathBuf], cx: &AppContext) -> bool {
|
pub fn visibility_for_paths(&self, paths: &[PathBuf], cx: &AppContext) -> Option<bool> {
|
||||||
paths.iter().all(|path| self.contains_path(path, cx))
|
paths
|
||||||
|
.iter()
|
||||||
|
.map(|path| self.visibility_for_path(path, cx))
|
||||||
|
.max()
|
||||||
|
.flatten()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn contains_path(&self, path: &Path, cx: &AppContext) -> bool {
|
pub fn visibility_for_path(&self, path: &Path, cx: &AppContext) -> Option<bool> {
|
||||||
for worktree in self.worktrees() {
|
self.worktrees()
|
||||||
let worktree = worktree.read(cx).as_local();
|
.filter_map(|worktree| {
|
||||||
if worktree.map_or(false, |w| w.contains_abs_path(path)) {
|
let worktree = worktree.read(cx);
|
||||||
return true;
|
worktree
|
||||||
}
|
.as_local()?
|
||||||
}
|
.contains_abs_path(path)
|
||||||
false
|
.then(|| worktree.is_visible())
|
||||||
|
})
|
||||||
|
.max()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_entry(
|
pub fn create_entry(
|
||||||
|
@ -493,9 +493,16 @@ mod tests {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
cx.update(|cx| open_paths(&[PathBuf::from("/dir/main.ts")], app_state, None, cx))
|
cx.update(|cx| {
|
||||||
.await
|
open_paths(
|
||||||
.unwrap();
|
&[PathBuf::from("/dir/main.ts")],
|
||||||
|
app_state,
|
||||||
|
workspace::OpenOptions::default(),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
assert_eq!(cx.update(|cx| cx.windows().len()), 1);
|
assert_eq!(cx.update(|cx| cx.windows().len()), 1);
|
||||||
|
|
||||||
let workspace = cx.update(|cx| cx.windows()[0].downcast::<Workspace>().unwrap());
|
let workspace = cx.update(|cx| cx.windows()[0].downcast::<Workspace>().unwrap());
|
||||||
|
@ -262,7 +262,8 @@ pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
|
|||||||
cx.spawn(move |cx| async move {
|
cx.spawn(move |cx| async move {
|
||||||
if let Some(paths) = paths.await.log_err().flatten() {
|
if let Some(paths) = paths.await.log_err().flatten() {
|
||||||
cx.update(|cx| {
|
cx.update(|cx| {
|
||||||
open_paths(&paths, app_state, None, cx).detach_and_log_err(cx)
|
open_paths(&paths, app_state, OpenOptions::default(), cx)
|
||||||
|
.detach_and_log_err(cx)
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
@ -1414,8 +1415,18 @@ impl Workspace {
|
|||||||
let app_state = self.app_state.clone();
|
let app_state = self.app_state.clone();
|
||||||
|
|
||||||
cx.spawn(|_, mut cx| async move {
|
cx.spawn(|_, mut cx| async move {
|
||||||
cx.update(|cx| open_paths(&paths, app_state, window_to_replace, cx))?
|
cx.update(|cx| {
|
||||||
.await?;
|
open_paths(
|
||||||
|
&paths,
|
||||||
|
app_state,
|
||||||
|
OpenOptions {
|
||||||
|
replace_window: window_to_replace,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})?
|
||||||
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -4361,6 +4372,13 @@ pub async fn get_any_active_workspace(
|
|||||||
|
|
||||||
fn activate_any_workspace_window(cx: &mut AsyncAppContext) -> Option<WindowHandle<Workspace>> {
|
fn activate_any_workspace_window(cx: &mut AsyncAppContext) -> Option<WindowHandle<Workspace>> {
|
||||||
cx.update(|cx| {
|
cx.update(|cx| {
|
||||||
|
if let Some(workspace_window) = cx
|
||||||
|
.active_window()
|
||||||
|
.and_then(|window| window.downcast::<Workspace>())
|
||||||
|
{
|
||||||
|
return Some(workspace_window);
|
||||||
|
}
|
||||||
|
|
||||||
for window in cx.windows() {
|
for window in cx.windows() {
|
||||||
if let Some(workspace_window) = window.downcast::<Workspace>() {
|
if let Some(workspace_window) = window.downcast::<Workspace>() {
|
||||||
workspace_window
|
workspace_window
|
||||||
@ -4375,11 +4393,17 @@ fn activate_any_workspace_window(cx: &mut AsyncAppContext) -> Option<WindowHandl
|
|||||||
.flatten()
|
.flatten()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct OpenOptions {
|
||||||
|
pub open_new_workspace: Option<bool>,
|
||||||
|
pub replace_window: Option<WindowHandle<Workspace>>,
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
pub fn open_paths(
|
pub fn open_paths(
|
||||||
abs_paths: &[PathBuf],
|
abs_paths: &[PathBuf],
|
||||||
app_state: Arc<AppState>,
|
app_state: Arc<AppState>,
|
||||||
requesting_window: Option<WindowHandle<Workspace>>,
|
open_options: OpenOptions,
|
||||||
cx: &mut AppContext,
|
cx: &mut AppContext,
|
||||||
) -> Task<
|
) -> Task<
|
||||||
anyhow::Result<(
|
anyhow::Result<(
|
||||||
@ -4388,24 +4412,62 @@ pub fn open_paths(
|
|||||||
)>,
|
)>,
|
||||||
> {
|
> {
|
||||||
let abs_paths = abs_paths.to_vec();
|
let abs_paths = abs_paths.to_vec();
|
||||||
// Open paths in existing workspace if possible
|
let mut existing = None;
|
||||||
let existing = activate_workspace_for_project(cx, {
|
let mut best_match = None;
|
||||||
let abs_paths = abs_paths.clone();
|
let mut open_visible = OpenVisible::All;
|
||||||
move |project, cx| project.contains_paths(&abs_paths, cx)
|
|
||||||
});
|
if open_options.open_new_workspace != Some(true) {
|
||||||
|
for window in cx.windows() {
|
||||||
|
let Some(handle) = window.downcast::<Workspace>() else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if let Ok(workspace) = handle.read(cx) {
|
||||||
|
let m = workspace
|
||||||
|
.project()
|
||||||
|
.read(cx)
|
||||||
|
.visibility_for_paths(&abs_paths, cx);
|
||||||
|
if m > best_match {
|
||||||
|
existing = Some(handle);
|
||||||
|
best_match = m;
|
||||||
|
} else if best_match.is_none() && open_options.open_new_workspace == Some(false) {
|
||||||
|
existing = Some(handle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
cx.spawn(move |mut cx| async move {
|
cx.spawn(move |mut cx| async move {
|
||||||
|
if open_options.open_new_workspace.is_none() && existing.is_none() {
|
||||||
|
let all_files = abs_paths.iter().map(|path| app_state.fs.metadata(path));
|
||||||
|
if futures::future::join_all(all_files)
|
||||||
|
.await
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|result| result.ok().flatten())
|
||||||
|
.all(|file| !file.is_dir)
|
||||||
|
{
|
||||||
|
existing = activate_any_workspace_window(&mut cx);
|
||||||
|
open_visible = OpenVisible::None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(existing) = existing {
|
if let Some(existing) = existing {
|
||||||
Ok((
|
Ok((
|
||||||
existing,
|
existing,
|
||||||
existing
|
existing
|
||||||
.update(&mut cx, |workspace, cx| {
|
.update(&mut cx, |workspace, cx| {
|
||||||
workspace.open_paths(abs_paths, OpenVisible::All, None, cx)
|
cx.activate_window();
|
||||||
|
workspace.open_paths(abs_paths, open_visible, None, cx)
|
||||||
})?
|
})?
|
||||||
.await,
|
.await,
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
cx.update(move |cx| {
|
cx.update(move |cx| {
|
||||||
Workspace::new_local(abs_paths, app_state.clone(), requesting_window, cx)
|
Workspace::new_local(
|
||||||
|
abs_paths,
|
||||||
|
app_state.clone(),
|
||||||
|
open_options.replace_window,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
})?
|
})?
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
@ -264,24 +264,14 @@ fn main() {
|
|||||||
cx.set_menus(app_menus());
|
cx.set_menus(app_menus());
|
||||||
initialize_workspace(app_state.clone(), cx);
|
initialize_workspace(app_state.clone(), cx);
|
||||||
|
|
||||||
if stdout_is_a_pty() {
|
// todo(linux): unblock this
|
||||||
// todo(linux): unblock this
|
upload_panics_and_crashes(http.clone(), cx);
|
||||||
#[cfg(not(target_os = "linux"))]
|
|
||||||
upload_panics_and_crashes(http.clone(), cx);
|
cx.activate(true);
|
||||||
cx.activate(true);
|
|
||||||
let urls = collect_url_args(cx);
|
let urls = collect_url_args(cx);
|
||||||
if !urls.is_empty() {
|
if !urls.is_empty() {
|
||||||
listener.open_urls(urls)
|
listener.open_urls(urls)
|
||||||
}
|
|
||||||
} else {
|
|
||||||
upload_panics_and_crashes(http.clone(), cx);
|
|
||||||
// TODO Development mode that forces the CLI mode usually runs Zed binary as is instead
|
|
||||||
// of an *app, hence gets no specific callbacks run. Emulate them here, if needed.
|
|
||||||
if std::env::var(FORCE_CLI_MODE_ENV_VAR_NAME).ok().is_some()
|
|
||||||
&& !listener.triggered.load(Ordering::Acquire)
|
|
||||||
{
|
|
||||||
listener.open_urls(collect_url_args(cx))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut triggered_authentication = false;
|
let mut triggered_authentication = false;
|
||||||
@ -339,8 +329,13 @@ fn handle_open_request(
|
|||||||
if !request.open_paths.is_empty() {
|
if !request.open_paths.is_empty() {
|
||||||
let app_state = app_state.clone();
|
let app_state = app_state.clone();
|
||||||
task = Some(cx.spawn(|mut cx| async move {
|
task = Some(cx.spawn(|mut cx| async move {
|
||||||
let (_window, results) =
|
let (_window, results) = open_paths_with_positions(
|
||||||
open_paths_with_positions(&request.open_paths, app_state, &mut cx).await?;
|
&request.open_paths,
|
||||||
|
app_state,
|
||||||
|
workspace::OpenOptions::default(),
|
||||||
|
&mut cx,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
for result in results.into_iter().flatten() {
|
for result in results.into_iter().flatten() {
|
||||||
if let Err(err) = result {
|
if let Err(err) = result {
|
||||||
log::error!("Error opening path: {err}",);
|
log::error!("Error opening path: {err}",);
|
||||||
@ -441,9 +436,16 @@ async fn installation_id() -> Result<(String, bool)> {
|
|||||||
async fn restore_or_create_workspace(app_state: Arc<AppState>, cx: AsyncAppContext) {
|
async fn restore_or_create_workspace(app_state: Arc<AppState>, cx: AsyncAppContext) {
|
||||||
async_maybe!({
|
async_maybe!({
|
||||||
if let Some(location) = workspace::last_opened_workspace_paths().await {
|
if let Some(location) = workspace::last_opened_workspace_paths().await {
|
||||||
cx.update(|cx| workspace::open_paths(location.paths().as_ref(), app_state, None, cx))?
|
cx.update(|cx| {
|
||||||
.await
|
workspace::open_paths(
|
||||||
.log_err();
|
location.paths().as_ref(),
|
||||||
|
app_state,
|
||||||
|
workspace::OpenOptions::default(),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})?
|
||||||
|
.await
|
||||||
|
.log_err();
|
||||||
} else if matches!(KEY_VALUE_STORE.read_kvp(FIRST_OPEN), Ok(None)) {
|
} else if matches!(KEY_VALUE_STORE.read_kvp(FIRST_OPEN), Ok(None)) {
|
||||||
cx.update(|cx| show_welcome_view(app_state, cx)).log_err();
|
cx.update(|cx| show_welcome_view(app_state, cx)).log_err();
|
||||||
} else {
|
} else {
|
||||||
@ -901,7 +903,7 @@ fn collect_url_args(cx: &AppContext) -> Vec<String> {
|
|||||||
.filter_map(|arg| match std::fs::canonicalize(Path::new(&arg)) {
|
.filter_map(|arg| match std::fs::canonicalize(Path::new(&arg)) {
|
||||||
Ok(path) => Some(format!("file://{}", path.to_string_lossy())),
|
Ok(path) => Some(format!("file://{}", path.to_string_lossy())),
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
if arg.starts_with("file://") {
|
if arg.starts_with("file://") || arg.starts_with("zed-cli://") {
|
||||||
Some(arg)
|
Some(arg)
|
||||||
} else if let Some(_) = parse_zed_link(&arg, cx) {
|
} else if let Some(_) = parse_zed_link(&arg, cx) {
|
||||||
Some(arg)
|
Some(arg)
|
||||||
|
@ -11,11 +11,10 @@ use futures::{FutureExt, SinkExt, StreamExt};
|
|||||||
use gpui::{AppContext, AsyncAppContext, Global, WindowHandle};
|
use gpui::{AppContext, AsyncAppContext, Global, WindowHandle};
|
||||||
use language::{Bias, Point};
|
use language::{Bias, Point};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::atomic::Ordering;
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::{path::PathBuf, sync::atomic::AtomicBool};
|
|
||||||
use util::paths::PathLikeWithPosition;
|
use util::paths::PathLikeWithPosition;
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use workspace::item::ItemHandle;
|
use workspace::item::ItemHandle;
|
||||||
@ -89,7 +88,6 @@ impl OpenRequest {
|
|||||||
|
|
||||||
pub struct OpenListener {
|
pub struct OpenListener {
|
||||||
tx: UnboundedSender<Vec<String>>,
|
tx: UnboundedSender<Vec<String>>,
|
||||||
pub triggered: AtomicBool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct GlobalOpenListener(Arc<OpenListener>);
|
struct GlobalOpenListener(Arc<OpenListener>);
|
||||||
@ -107,17 +105,10 @@ impl OpenListener {
|
|||||||
|
|
||||||
pub fn new() -> (Self, UnboundedReceiver<Vec<String>>) {
|
pub fn new() -> (Self, UnboundedReceiver<Vec<String>>) {
|
||||||
let (tx, rx) = mpsc::unbounded();
|
let (tx, rx) = mpsc::unbounded();
|
||||||
(
|
(OpenListener { tx }, rx)
|
||||||
OpenListener {
|
|
||||||
tx,
|
|
||||||
triggered: AtomicBool::new(false),
|
|
||||||
},
|
|
||||||
rx,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn open_urls(&self, urls: Vec<String>) {
|
pub fn open_urls(&self, urls: Vec<String>) {
|
||||||
self.triggered.store(true, Ordering::Release);
|
|
||||||
self.tx
|
self.tx
|
||||||
.unbounded_send(urls)
|
.unbounded_send(urls)
|
||||||
.map_err(|_| anyhow!("no listener for open requests"))
|
.map_err(|_| anyhow!("no listener for open requests"))
|
||||||
@ -157,6 +148,7 @@ fn connect_to_cli(
|
|||||||
pub async fn open_paths_with_positions(
|
pub async fn open_paths_with_positions(
|
||||||
path_likes: &Vec<PathLikeWithPosition<PathBuf>>,
|
path_likes: &Vec<PathLikeWithPosition<PathBuf>>,
|
||||||
app_state: Arc<AppState>,
|
app_state: Arc<AppState>,
|
||||||
|
open_options: workspace::OpenOptions,
|
||||||
cx: &mut AsyncAppContext,
|
cx: &mut AsyncAppContext,
|
||||||
) -> Result<(
|
) -> Result<(
|
||||||
WindowHandle<Workspace>,
|
WindowHandle<Workspace>,
|
||||||
@ -180,7 +172,7 @@ pub async fn open_paths_with_positions(
|
|||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let (workspace, items) = cx
|
let (workspace, items) = cx
|
||||||
.update(|cx| workspace::open_paths(&paths, app_state, None, cx))?
|
.update(|cx| workspace::open_paths(&paths, app_state, open_options, cx))?
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
for (item, path) in items.iter().zip(&paths) {
|
for (item, path) in items.iter().zip(&paths) {
|
||||||
@ -215,22 +207,30 @@ pub async fn handle_cli_connection(
|
|||||||
) {
|
) {
|
||||||
if let Some(request) = requests.next().await {
|
if let Some(request) = requests.next().await {
|
||||||
match request {
|
match request {
|
||||||
CliRequest::Open { paths, wait } => {
|
CliRequest::Open {
|
||||||
|
paths,
|
||||||
|
wait,
|
||||||
|
open_new_workspace,
|
||||||
|
} => {
|
||||||
let paths = if paths.is_empty() {
|
let paths = if paths.is_empty() {
|
||||||
workspace::last_opened_workspace_paths()
|
if open_new_workspace == Some(true) {
|
||||||
.await
|
vec![]
|
||||||
.map(|location| {
|
} else {
|
||||||
location
|
workspace::last_opened_workspace_paths()
|
||||||
.paths()
|
.await
|
||||||
.iter()
|
.map(|location| {
|
||||||
.map(|path| PathLikeWithPosition {
|
location
|
||||||
path_like: path.clone(),
|
.paths()
|
||||||
row: None,
|
.iter()
|
||||||
column: None,
|
.map(|path| PathLikeWithPosition {
|
||||||
})
|
path_like: path.clone(),
|
||||||
.collect::<Vec<_>>()
|
row: None,
|
||||||
})
|
column: None,
|
||||||
.unwrap_or_default()
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
})
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
paths
|
paths
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@ -250,7 +250,17 @@ pub async fn handle_cli_connection(
|
|||||||
|
|
||||||
let mut errored = false;
|
let mut errored = false;
|
||||||
|
|
||||||
match open_paths_with_positions(&paths, app_state, &mut cx).await {
|
match open_paths_with_positions(
|
||||||
|
&paths,
|
||||||
|
app_state,
|
||||||
|
workspace::OpenOptions {
|
||||||
|
open_new_workspace,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
&mut cx,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
Ok((workspace, items)) => {
|
Ok((workspace, items)) => {
|
||||||
let mut item_release_futures = Vec::new();
|
let mut item_release_futures = Vec::new();
|
||||||
|
|
||||||
|
@ -905,6 +905,10 @@ mod tests {
|
|||||||
"da": null,
|
"da": null,
|
||||||
"db": null,
|
"db": null,
|
||||||
},
|
},
|
||||||
|
"e": {
|
||||||
|
"ea": null,
|
||||||
|
"eb": null,
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
@ -913,7 +917,7 @@ mod tests {
|
|||||||
open_paths(
|
open_paths(
|
||||||
&[PathBuf::from("/root/a"), PathBuf::from("/root/b")],
|
&[PathBuf::from("/root/a"), PathBuf::from("/root/b")],
|
||||||
app_state.clone(),
|
app_state.clone(),
|
||||||
None,
|
workspace::OpenOptions::default(),
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@ -921,9 +925,16 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(cx.read(|cx| cx.windows().len()), 1);
|
assert_eq!(cx.read(|cx| cx.windows().len()), 1);
|
||||||
|
|
||||||
cx.update(|cx| open_paths(&[PathBuf::from("/root/a")], app_state.clone(), None, cx))
|
cx.update(|cx| {
|
||||||
.await
|
open_paths(
|
||||||
.unwrap();
|
&[PathBuf::from("/root/a")],
|
||||||
|
app_state.clone(),
|
||||||
|
workspace::OpenOptions::default(),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
assert_eq!(cx.read(|cx| cx.windows().len()), 1);
|
assert_eq!(cx.read(|cx| cx.windows().len()), 1);
|
||||||
let workspace_1 = cx
|
let workspace_1 = cx
|
||||||
.read(|cx| cx.windows()[0].downcast::<Workspace>())
|
.read(|cx| cx.windows()[0].downcast::<Workspace>())
|
||||||
@ -942,9 +953,9 @@ mod tests {
|
|||||||
|
|
||||||
cx.update(|cx| {
|
cx.update(|cx| {
|
||||||
open_paths(
|
open_paths(
|
||||||
&[PathBuf::from("/root/b"), PathBuf::from("/root/c")],
|
&[PathBuf::from("/root/c"), PathBuf::from("/root/d")],
|
||||||
app_state.clone(),
|
app_state.clone(),
|
||||||
None,
|
workspace::OpenOptions::default(),
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@ -958,9 +969,12 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
cx.update(|cx| {
|
cx.update(|cx| {
|
||||||
open_paths(
|
open_paths(
|
||||||
&[PathBuf::from("/root/c"), PathBuf::from("/root/d")],
|
&[PathBuf::from("/root/e")],
|
||||||
app_state,
|
app_state,
|
||||||
Some(window),
|
workspace::OpenOptions {
|
||||||
|
replace_window: Some(window),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@ -978,7 +992,7 @@ mod tests {
|
|||||||
.worktrees(cx)
|
.worktrees(cx)
|
||||||
.map(|w| w.read(cx).abs_path())
|
.map(|w| w.read(cx).abs_path())
|
||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
&[Path::new("/root/c").into(), Path::new("/root/d").into()]
|
&[Path::new("/root/e").into()]
|
||||||
);
|
);
|
||||||
assert!(workspace.left_dock().read(cx).is_open());
|
assert!(workspace.left_dock().read(cx).is_open());
|
||||||
assert!(workspace.active_pane().focus_handle(cx).is_focused(cx));
|
assert!(workspace.active_pane().focus_handle(cx).is_focused(cx));
|
||||||
@ -986,6 +1000,123 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_open_add_new(cx: &mut TestAppContext) {
|
||||||
|
let app_state = init_test(cx);
|
||||||
|
app_state
|
||||||
|
.fs
|
||||||
|
.as_fake()
|
||||||
|
.insert_tree("/root", json!({"a": "hey", "b": "", "dir": {"c": "f"}}))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
cx.update(|cx| {
|
||||||
|
open_paths(
|
||||||
|
&[PathBuf::from("/root/dir")],
|
||||||
|
app_state.clone(),
|
||||||
|
workspace::OpenOptions::default(),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(cx.update(|cx| cx.windows().len()), 1);
|
||||||
|
|
||||||
|
cx.update(|cx| {
|
||||||
|
open_paths(
|
||||||
|
&[PathBuf::from("/root/a")],
|
||||||
|
app_state.clone(),
|
||||||
|
workspace::OpenOptions {
|
||||||
|
open_new_workspace: Some(false),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(cx.update(|cx| cx.windows().len()), 1);
|
||||||
|
|
||||||
|
cx.update(|cx| {
|
||||||
|
open_paths(
|
||||||
|
&[PathBuf::from("/root/dir/c")],
|
||||||
|
app_state.clone(),
|
||||||
|
workspace::OpenOptions {
|
||||||
|
open_new_workspace: Some(true),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(cx.update(|cx| cx.windows().len()), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_open_file_in_many_spaces(cx: &mut TestAppContext) {
|
||||||
|
let app_state = init_test(cx);
|
||||||
|
app_state
|
||||||
|
.fs
|
||||||
|
.as_fake()
|
||||||
|
.insert_tree("/root", json!({"dir1": {"a": "b"}, "dir2": {"c": "d"}}))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
cx.update(|cx| {
|
||||||
|
open_paths(
|
||||||
|
&[PathBuf::from("/root/dir1/a")],
|
||||||
|
app_state.clone(),
|
||||||
|
workspace::OpenOptions::default(),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(cx.update(|cx| cx.windows().len()), 1);
|
||||||
|
let window1 = cx.update(|cx| cx.active_window().unwrap());
|
||||||
|
|
||||||
|
cx.update(|cx| {
|
||||||
|
open_paths(
|
||||||
|
&[PathBuf::from("/root/dir2/c")],
|
||||||
|
app_state.clone(),
|
||||||
|
workspace::OpenOptions::default(),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(cx.update(|cx| cx.windows().len()), 1);
|
||||||
|
|
||||||
|
cx.update(|cx| {
|
||||||
|
open_paths(
|
||||||
|
&[PathBuf::from("/root/dir2")],
|
||||||
|
app_state.clone(),
|
||||||
|
workspace::OpenOptions::default(),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(cx.update(|cx| cx.windows().len()), 2);
|
||||||
|
let window2 = cx.update(|cx| cx.active_window().unwrap());
|
||||||
|
assert!(window1 != window2);
|
||||||
|
cx.update_window(window1, |_, cx| cx.activate_window())
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
cx.update(|cx| {
|
||||||
|
open_paths(
|
||||||
|
&[PathBuf::from("/root/dir2/c")],
|
||||||
|
app_state.clone(),
|
||||||
|
workspace::OpenOptions::default(),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(cx.update(|cx| cx.windows().len()), 2);
|
||||||
|
// should have opened in window2 because that has dir2 visibly open (window1 has it open, but not in the project panel)
|
||||||
|
assert!(cx.update(|cx| cx.active_window().unwrap()) == window2);
|
||||||
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_window_edit_state(cx: &mut TestAppContext) {
|
async fn test_window_edit_state(cx: &mut TestAppContext) {
|
||||||
let executor = cx.executor();
|
let executor = cx.executor();
|
||||||
@ -996,9 +1127,16 @@ mod tests {
|
|||||||
.insert_tree("/root", json!({"a": "hey"}))
|
.insert_tree("/root", json!({"a": "hey"}))
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
cx.update(|cx| open_paths(&[PathBuf::from("/root/a")], app_state.clone(), None, cx))
|
cx.update(|cx| {
|
||||||
.await
|
open_paths(
|
||||||
.unwrap();
|
&[PathBuf::from("/root/a")],
|
||||||
|
app_state.clone(),
|
||||||
|
workspace::OpenOptions::default(),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
assert_eq!(cx.update(|cx| cx.windows().len()), 1);
|
assert_eq!(cx.update(|cx| cx.windows().len()), 1);
|
||||||
|
|
||||||
// When opening the workspace, the window is not in a edited state.
|
// When opening the workspace, the window is not in a edited state.
|
||||||
@ -1063,9 +1201,16 @@ mod tests {
|
|||||||
assert!(!window_is_edited(window, cx));
|
assert!(!window_is_edited(window, cx));
|
||||||
|
|
||||||
// Opening the buffer again doesn't impact the window's edited state.
|
// Opening the buffer again doesn't impact the window's edited state.
|
||||||
cx.update(|cx| open_paths(&[PathBuf::from("/root/a")], app_state, None, cx))
|
cx.update(|cx| {
|
||||||
.await
|
open_paths(
|
||||||
.unwrap();
|
&[PathBuf::from("/root/a")],
|
||||||
|
app_state,
|
||||||
|
workspace::OpenOptions::default(),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
let editor = window
|
let editor = window
|
||||||
.read_with(cx, |workspace, cx| {
|
.read_with(cx, |workspace, cx| {
|
||||||
workspace
|
workspace
|
||||||
@ -1292,9 +1437,16 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
cx.update(|cx| open_paths(&[PathBuf::from("/dir1/")], app_state, None, cx))
|
cx.update(|cx| {
|
||||||
.await
|
open_paths(
|
||||||
.unwrap();
|
&[PathBuf::from("/dir1/")],
|
||||||
|
app_state,
|
||||||
|
workspace::OpenOptions::default(),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
assert_eq!(cx.update(|cx| cx.windows().len()), 1);
|
assert_eq!(cx.update(|cx| cx.windows().len()), 1);
|
||||||
let window = cx.update(|cx| cx.windows()[0].downcast::<Workspace>().unwrap());
|
let window = cx.update(|cx| cx.windows()[0].downcast::<Workspace>().unwrap());
|
||||||
let workspace = window.root(cx).unwrap();
|
let workspace = window.root(cx).unwrap();
|
||||||
@ -1526,7 +1678,14 @@ mod tests {
|
|||||||
Path::new("/root/excluded_dir/ignored_subdir").to_path_buf(),
|
Path::new("/root/excluded_dir/ignored_subdir").to_path_buf(),
|
||||||
];
|
];
|
||||||
let (opened_workspace, new_items) = cx
|
let (opened_workspace, new_items) = cx
|
||||||
.update(|cx| workspace::open_paths(&paths_to_open, app_state, None, cx))
|
.update(|cx| {
|
||||||
|
workspace::open_paths(
|
||||||
|
&paths_to_open,
|
||||||
|
app_state,
|
||||||
|
workspace::OpenOptions::default(),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user