Do not break Zed & Zed CLI compatibility

Kirill Bulatov created

Change summary

crates/cli/src/cli.rs    | 13 ++++++-------
crates/cli/src/main.rs   |  5 +++--
crates/util/src/paths.rs | 16 ++++++++++++++++
crates/zed/src/main.rs   | 20 ++++++++++++++++----
4 files changed, 41 insertions(+), 13 deletions(-)

Detailed changes

crates/cli/src/cli.rs 🔗

@@ -1,7 +1,5 @@
 pub use ipc_channel::ipc;
 use serde::{Deserialize, Serialize};
-use std::path::PathBuf;
-use util::paths::PathLikeWithPosition;
 
 #[derive(Serialize, Deserialize)]
 pub struct IpcHandshake {
@@ -11,11 +9,12 @@ pub struct IpcHandshake {
 
 #[derive(Debug, Serialize, Deserialize)]
 pub enum CliRequest {
-    Open {
-        // TODO kb old cli won't be able to communicate to new Zed with this change
-        paths: Vec<PathLikeWithPosition<PathBuf>>,
-        wait: bool,
-    },
+    // The filed is named `path` for compatibility, but now CLI can request
+    // opening a path at a certain row and/or column: `some/path:123` and `some/path:123:456`.
+    //
+    // Since Zed CLI has to be installed separately, there can be situations when old CLI is
+    // 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)]

crates/cli/src/main.rs 🔗

@@ -79,10 +79,11 @@ fn main() -> Result<()> {
             .paths_with_position
             .into_iter()
             .map(|path_with_position| {
-                path_with_position.convert_path(|path| {
+                let path_with_position = path_with_position.convert_path(|path| {
                     fs::canonicalize(&path)
                         .with_context(|| format!("path {path:?} canonicalization"))
-                })
+                })?;
+                Ok(path_with_position.to_string(|path| path.display().to_string()))
             })
             .collect::<Result<_>>()?,
         wait: args.wait,

crates/util/src/paths.rs 🔗

@@ -119,4 +119,20 @@ impl<P> PathLikeWithPosition<P> {
             column: self.column,
         })
     }
+
+    pub fn to_string<F>(&self, path_like_to_string: F) -> String
+    where
+        F: Fn(&P) -> String,
+    {
+        let path_like_string = path_like_to_string(&self.path_like);
+        if let Some(row) = self.row {
+            if let Some(column) = self.column {
+                format!("{path_like_string}:{row}:{column}")
+            } else {
+                format!("{path_like_string}:{row}")
+            }
+        } else {
+            path_like_string
+        }
+    }
 }

crates/zed/src/main.rs 🔗

@@ -37,7 +37,7 @@ use std::{
     io::Write as _,
     os::unix::prelude::OsStrExt,
     panic,
-    path::PathBuf,
+    path::{Path, PathBuf},
     sync::{
         atomic::{AtomicBool, Ordering},
         Arc, Weak,
@@ -48,7 +48,10 @@ use std::{
 use sum_tree::Bias;
 use terminal_view::{get_working_directory, TerminalView};
 use text::Point;
-use util::http::{self, HttpClient};
+use util::{
+    http::{self, HttpClient},
+    paths::PathLikeWithPosition,
+};
 use welcome::{show_welcome_experience, FIRST_OPEN};
 
 use fs::RealFs;
@@ -691,7 +694,16 @@ async fn handle_cli_connection(
                 } else {
                     paths
                         .into_iter()
-                        .map(|path_with_position| {
+                        .filter_map(|path_with_position_string| {
+                            let path_with_position = PathLikeWithPosition::parse_str(
+                                &path_with_position_string,
+                                |path_str| {
+                                    Ok::<_, std::convert::Infallible>(
+                                        Path::new(path_str).to_path_buf(),
+                                    )
+                                },
+                            )
+                            .expect("Infallible");
                             let path = path_with_position.path_like;
                             if let Some(row) = path_with_position.row {
                                 if path.is_file() {
@@ -701,7 +713,7 @@ async fn handle_cli_connection(
                                     caret_positions.insert(path.clone(), Point::new(row, col));
                                 }
                             }
-                            path
+                            Some(path)
                         })
                         .collect()
                 };