Change PathLikeWithPosition<P> into a non-generic type and replace ad-hoc Windows path parsing (#15373)
This simplifies `PathWithPosition` by making the common use case concrete and removing the manual, incomplete Windows path parsing. Windows paths also don't get '/'s replaced by '\\'s anymore to limit the responsibility of the code to just parsing out the suffix and creating `PathBuf` from the rest. `Path::file_name()` is now used to extract the filename and potential suffix instead of manual parsing from the full input. This way e.g. Windows paths that begin with a drive letter are handled correctly without platform-specific hacks. Release Notes: - N/A
This commit is contained in:
parent
41c550cbe1
commit
13dcb42c1c
8 changed files with 184 additions and 270 deletions
|
@ -5,14 +5,13 @@ use clap::Parser;
|
||||||
use cli::{ipc::IpcOneShotServer, CliRequest, CliResponse, IpcHandshake};
|
use cli::{ipc::IpcOneShotServer, CliRequest, CliResponse, IpcHandshake};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use std::{
|
use std::{
|
||||||
convert::Infallible,
|
|
||||||
env, fs, io,
|
env, fs, io,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
process::ExitStatus,
|
process::ExitStatus,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
thread::{self, JoinHandle},
|
thread::{self, JoinHandle},
|
||||||
};
|
};
|
||||||
use util::paths::PathLikeWithPosition;
|
use util::paths::PathWithPosition;
|
||||||
|
|
||||||
struct Detect;
|
struct Detect;
|
||||||
|
|
||||||
|
@ -54,13 +53,10 @@ struct Args {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_path_with_position(argument_str: &str) -> Result<String, std::io::Error> {
|
fn parse_path_with_position(argument_str: &str) -> Result<String, std::io::Error> {
|
||||||
let path_like = PathLikeWithPosition::parse_str::<Infallible>(argument_str, |_, path_str| {
|
let path = PathWithPosition::parse_str(argument_str);
|
||||||
Ok(Path::new(path_str).to_path_buf())
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
let curdir = env::current_dir()?;
|
let curdir = env::current_dir()?;
|
||||||
|
|
||||||
let canonicalized = path_like.map_path_like(|path| match fs::canonicalize(&path) {
|
let canonicalized = path.map_path(|path| match fs::canonicalize(&path) {
|
||||||
Ok(path) => Ok(path),
|
Ok(path) => Ok(path),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
if let Some(mut parent) = path.parent() {
|
if let Some(mut parent) = path.parent() {
|
||||||
|
|
|
@ -28,7 +28,7 @@ use std::{
|
||||||
};
|
};
|
||||||
use text::Point;
|
use text::Point;
|
||||||
use ui::{prelude::*, HighlightedLabel, ListItem, ListItemSpacing};
|
use ui::{prelude::*, HighlightedLabel, ListItem, ListItemSpacing};
|
||||||
use util::{paths::PathLikeWithPosition, post_inc, ResultExt};
|
use util::{paths::PathWithPosition, post_inc, ResultExt};
|
||||||
use workspace::{item::PreviewTabsSettings, ModalView, Workspace};
|
use workspace::{item::PreviewTabsSettings, ModalView, Workspace};
|
||||||
|
|
||||||
actions!(file_finder, [SelectPrev]);
|
actions!(file_finder, [SelectPrev]);
|
||||||
|
@ -158,7 +158,7 @@ pub struct FileFinderDelegate {
|
||||||
search_count: usize,
|
search_count: usize,
|
||||||
latest_search_id: usize,
|
latest_search_id: usize,
|
||||||
latest_search_did_cancel: bool,
|
latest_search_did_cancel: bool,
|
||||||
latest_search_query: Option<PathLikeWithPosition<FileSearchQuery>>,
|
latest_search_query: Option<FileSearchQuery>,
|
||||||
currently_opened_path: Option<FoundPath>,
|
currently_opened_path: Option<FoundPath>,
|
||||||
matches: Matches,
|
matches: Matches,
|
||||||
selected_index: usize,
|
selected_index: usize,
|
||||||
|
@ -226,7 +226,7 @@ impl Matches {
|
||||||
&'a mut self,
|
&'a mut self,
|
||||||
history_items: impl IntoIterator<Item = &'a FoundPath> + Clone,
|
history_items: impl IntoIterator<Item = &'a FoundPath> + Clone,
|
||||||
currently_opened: Option<&'a FoundPath>,
|
currently_opened: Option<&'a FoundPath>,
|
||||||
query: Option<&PathLikeWithPosition<FileSearchQuery>>,
|
query: Option<&FileSearchQuery>,
|
||||||
new_search_matches: impl Iterator<Item = ProjectPanelOrdMatch>,
|
new_search_matches: impl Iterator<Item = ProjectPanelOrdMatch>,
|
||||||
extend_old_matches: bool,
|
extend_old_matches: bool,
|
||||||
) {
|
) {
|
||||||
|
@ -303,7 +303,7 @@ impl Matches {
|
||||||
fn matching_history_item_paths<'a>(
|
fn matching_history_item_paths<'a>(
|
||||||
history_items: impl IntoIterator<Item = &'a FoundPath>,
|
history_items: impl IntoIterator<Item = &'a FoundPath>,
|
||||||
currently_opened: Option<&'a FoundPath>,
|
currently_opened: Option<&'a FoundPath>,
|
||||||
query: Option<&PathLikeWithPosition<FileSearchQuery>>,
|
query: Option<&FileSearchQuery>,
|
||||||
) -> HashMap<Arc<Path>, Option<ProjectPanelOrdMatch>> {
|
) -> HashMap<Arc<Path>, Option<ProjectPanelOrdMatch>> {
|
||||||
let Some(query) = query else {
|
let Some(query) = query else {
|
||||||
return history_items
|
return history_items
|
||||||
|
@ -351,7 +351,7 @@ fn matching_history_item_paths<'a>(
|
||||||
fuzzy::match_fixed_path_set(
|
fuzzy::match_fixed_path_set(
|
||||||
candidates,
|
candidates,
|
||||||
worktree.to_usize(),
|
worktree.to_usize(),
|
||||||
query.path_like.path_query(),
|
query.path_query(),
|
||||||
false,
|
false,
|
||||||
max_results,
|
max_results,
|
||||||
)
|
)
|
||||||
|
@ -400,6 +400,7 @@ pub enum Event {
|
||||||
struct FileSearchQuery {
|
struct FileSearchQuery {
|
||||||
raw_query: String,
|
raw_query: String,
|
||||||
file_query_end: Option<usize>,
|
file_query_end: Option<usize>,
|
||||||
|
path_position: PathWithPosition,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FileSearchQuery {
|
impl FileSearchQuery {
|
||||||
|
@ -456,7 +457,7 @@ impl FileFinderDelegate {
|
||||||
|
|
||||||
fn spawn_search(
|
fn spawn_search(
|
||||||
&mut self,
|
&mut self,
|
||||||
query: PathLikeWithPosition<FileSearchQuery>,
|
query: FileSearchQuery,
|
||||||
cx: &mut ViewContext<Picker<Self>>,
|
cx: &mut ViewContext<Picker<Self>>,
|
||||||
) -> Task<()> {
|
) -> Task<()> {
|
||||||
let relative_to = self
|
let relative_to = self
|
||||||
|
@ -491,7 +492,7 @@ impl FileFinderDelegate {
|
||||||
cx.spawn(|picker, mut cx| async move {
|
cx.spawn(|picker, mut cx| async move {
|
||||||
let matches = fuzzy::match_path_sets(
|
let matches = fuzzy::match_path_sets(
|
||||||
candidate_sets.as_slice(),
|
candidate_sets.as_slice(),
|
||||||
query.path_like.path_query(),
|
query.path_query(),
|
||||||
relative_to,
|
relative_to,
|
||||||
false,
|
false,
|
||||||
100,
|
100,
|
||||||
|
@ -516,18 +517,18 @@ impl FileFinderDelegate {
|
||||||
&mut self,
|
&mut self,
|
||||||
search_id: usize,
|
search_id: usize,
|
||||||
did_cancel: bool,
|
did_cancel: bool,
|
||||||
query: PathLikeWithPosition<FileSearchQuery>,
|
query: FileSearchQuery,
|
||||||
matches: impl IntoIterator<Item = ProjectPanelOrdMatch>,
|
matches: impl IntoIterator<Item = ProjectPanelOrdMatch>,
|
||||||
cx: &mut ViewContext<Picker<Self>>,
|
cx: &mut ViewContext<Picker<Self>>,
|
||||||
) {
|
) {
|
||||||
if search_id >= self.latest_search_id {
|
if search_id >= self.latest_search_id {
|
||||||
self.latest_search_id = search_id;
|
self.latest_search_id = search_id;
|
||||||
let extend_old_matches = self.latest_search_did_cancel
|
let extend_old_matches = self.latest_search_did_cancel
|
||||||
&& Some(query.path_like.path_query())
|
&& Some(query.path_query())
|
||||||
== self
|
== self
|
||||||
.latest_search_query
|
.latest_search_query
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|query| query.path_like.path_query());
|
.map(|query| query.path_query());
|
||||||
self.matches.push_new_matches(
|
self.matches.push_new_matches(
|
||||||
&self.history_items,
|
&self.history_items,
|
||||||
self.currently_opened_path.as_ref(),
|
self.currently_opened_path.as_ref(),
|
||||||
|
@ -658,7 +659,7 @@ impl FileFinderDelegate {
|
||||||
|
|
||||||
fn lookup_absolute_path(
|
fn lookup_absolute_path(
|
||||||
&self,
|
&self,
|
||||||
query: PathLikeWithPosition<FileSearchQuery>,
|
query: FileSearchQuery,
|
||||||
cx: &mut ViewContext<'_, Picker<Self>>,
|
cx: &mut ViewContext<'_, Picker<Self>>,
|
||||||
) -> Task<()> {
|
) -> Task<()> {
|
||||||
cx.spawn(|picker, mut cx| async move {
|
cx.spawn(|picker, mut cx| async move {
|
||||||
|
@ -672,7 +673,7 @@ impl FileFinderDelegate {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let query_path = Path::new(query.path_like.path_query());
|
let query_path = Path::new(query.path_query());
|
||||||
let mut path_matches = Vec::new();
|
let mut path_matches = Vec::new();
|
||||||
match fs.metadata(query_path).await.log_err() {
|
match fs.metadata(query_path).await.log_err() {
|
||||||
Some(Some(_metadata)) => {
|
Some(Some(_metadata)) => {
|
||||||
|
@ -796,20 +797,20 @@ impl PickerDelegate for FileFinderDelegate {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
Task::ready(())
|
Task::ready(())
|
||||||
} else {
|
} else {
|
||||||
let query =
|
let path_position = PathWithPosition::parse_str(&raw_query);
|
||||||
PathLikeWithPosition::parse_str(&raw_query, |normalized_query, path_like_str| {
|
|
||||||
Ok::<_, std::convert::Infallible>(FileSearchQuery {
|
|
||||||
raw_query: normalized_query.to_owned(),
|
|
||||||
file_query_end: if path_like_str == raw_query {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(path_like_str.len())
|
|
||||||
},
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.expect("infallible");
|
|
||||||
|
|
||||||
if Path::new(query.path_like.path_query()).is_absolute() {
|
let query = FileSearchQuery {
|
||||||
|
raw_query: raw_query.trim().to_owned(),
|
||||||
|
file_query_end: if path_position.path.to_str().unwrap_or(raw_query) == raw_query {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
// Safe to unwrap as we won't get here when the unwrap in if fails
|
||||||
|
Some(path_position.path.to_str().unwrap().len())
|
||||||
|
},
|
||||||
|
path_position,
|
||||||
|
};
|
||||||
|
|
||||||
|
if Path::new(query.path_query()).is_absolute() {
|
||||||
self.lookup_absolute_path(query, cx)
|
self.lookup_absolute_path(query, cx)
|
||||||
} else {
|
} else {
|
||||||
self.spawn_search(query, cx)
|
self.spawn_search(query, cx)
|
||||||
|
@ -898,12 +899,12 @@ impl PickerDelegate for FileFinderDelegate {
|
||||||
let row = self
|
let row = self
|
||||||
.latest_search_query
|
.latest_search_query
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|query| query.row)
|
.and_then(|query| query.path_position.row)
|
||||||
.map(|row| row.saturating_sub(1));
|
.map(|row| row.saturating_sub(1));
|
||||||
let col = self
|
let col = self
|
||||||
.latest_search_query
|
.latest_search_query
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|query| query.column)
|
.and_then(|query| query.path_position.column)
|
||||||
.unwrap_or(0)
|
.unwrap_or(0)
|
||||||
.saturating_sub(1);
|
.saturating_sub(1);
|
||||||
let finder = self.file_finder.clone();
|
let finder = self.file_finder.clone();
|
||||||
|
|
|
@ -226,13 +226,13 @@ async fn test_row_column_numbers_query_inside_file(cx: &mut TestAppContext) {
|
||||||
.latest_search_query
|
.latest_search_query
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.expect("Finder should have a query after the update_matches call");
|
.expect("Finder should have a query after the update_matches call");
|
||||||
assert_eq!(latest_search_query.path_like.raw_query, query_inside_file);
|
assert_eq!(latest_search_query.raw_query, query_inside_file);
|
||||||
|
assert_eq!(latest_search_query.file_query_end, Some(file_query.len()));
|
||||||
|
assert_eq!(latest_search_query.path_position.row, Some(file_row));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
latest_search_query.path_like.file_query_end,
|
latest_search_query.path_position.column,
|
||||||
Some(file_query.len())
|
Some(file_column as u32)
|
||||||
);
|
);
|
||||||
assert_eq!(latest_search_query.row, Some(file_row));
|
|
||||||
assert_eq!(latest_search_query.column, Some(file_column as u32));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.dispatch_action(SelectNext);
|
cx.dispatch_action(SelectNext);
|
||||||
|
@ -301,13 +301,13 @@ async fn test_row_column_numbers_query_outside_file(cx: &mut TestAppContext) {
|
||||||
.latest_search_query
|
.latest_search_query
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.expect("Finder should have a query after the update_matches call");
|
.expect("Finder should have a query after the update_matches call");
|
||||||
assert_eq!(latest_search_query.path_like.raw_query, query_outside_file);
|
assert_eq!(latest_search_query.raw_query, query_outside_file);
|
||||||
|
assert_eq!(latest_search_query.file_query_end, Some(file_query.len()));
|
||||||
|
assert_eq!(latest_search_query.path_position.row, Some(file_row));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
latest_search_query.path_like.file_query_end,
|
latest_search_query.path_position.column,
|
||||||
Some(file_query.len())
|
Some(file_column as u32)
|
||||||
);
|
);
|
||||||
assert_eq!(latest_search_query.row, Some(file_row));
|
|
||||||
assert_eq!(latest_search_query.column, Some(file_column as u32));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.dispatch_action(SelectNext);
|
cx.dispatch_action(SelectNext);
|
||||||
|
@ -357,7 +357,7 @@ async fn test_matching_cancellation(cx: &mut TestAppContext) {
|
||||||
|
|
||||||
let (picker, _, cx) = build_find_picker(project, cx);
|
let (picker, _, cx) = build_find_picker(project, cx);
|
||||||
|
|
||||||
let query = test_path_like("hi");
|
let query = test_path_position("hi");
|
||||||
picker
|
picker
|
||||||
.update(cx, |picker, cx| {
|
.update(cx, |picker, cx| {
|
||||||
picker.delegate.spawn_search(query.clone(), cx)
|
picker.delegate.spawn_search(query.clone(), cx)
|
||||||
|
@ -450,7 +450,7 @@ async fn test_ignored_root(cx: &mut TestAppContext) {
|
||||||
|
|
||||||
picker
|
picker
|
||||||
.update(cx, |picker, cx| {
|
.update(cx, |picker, cx| {
|
||||||
picker.delegate.spawn_search(test_path_like("hi"), cx)
|
picker.delegate.spawn_search(test_path_position("hi"), cx)
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
picker.update(cx, |picker, _| assert_eq!(picker.delegate.matches.len(), 7));
|
picker.update(cx, |picker, _| assert_eq!(picker.delegate.matches.len(), 7));
|
||||||
|
@ -478,7 +478,7 @@ async fn test_single_file_worktrees(cx: &mut TestAppContext) {
|
||||||
// is included in the matching, because the worktree is a single file.
|
// is included in the matching, because the worktree is a single file.
|
||||||
picker
|
picker
|
||||||
.update(cx, |picker, cx| {
|
.update(cx, |picker, cx| {
|
||||||
picker.delegate.spawn_search(test_path_like("thf"), cx)
|
picker.delegate.spawn_search(test_path_position("thf"), cx)
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
cx.read(|cx| {
|
cx.read(|cx| {
|
||||||
|
@ -499,7 +499,7 @@ async fn test_single_file_worktrees(cx: &mut TestAppContext) {
|
||||||
// not match anything.
|
// not match anything.
|
||||||
picker
|
picker
|
||||||
.update(cx, |f, cx| {
|
.update(cx, |f, cx| {
|
||||||
f.delegate.spawn_search(test_path_like("thf/"), cx)
|
f.delegate.spawn_search(test_path_position("thf/"), cx)
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
picker.update(cx, |f, _| assert_eq!(f.delegate.matches.len(), 0));
|
picker.update(cx, |f, _| assert_eq!(f.delegate.matches.len(), 0));
|
||||||
|
@ -548,7 +548,7 @@ async fn test_path_distance_ordering(cx: &mut TestAppContext) {
|
||||||
let finder = open_file_picker(&workspace, cx);
|
let finder = open_file_picker(&workspace, cx);
|
||||||
finder
|
finder
|
||||||
.update(cx, |f, cx| {
|
.update(cx, |f, cx| {
|
||||||
f.delegate.spawn_search(test_path_like("a.txt"), cx)
|
f.delegate.spawn_search(test_path_position("a.txt"), cx)
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
@ -581,7 +581,7 @@ async fn test_search_worktree_without_files(cx: &mut TestAppContext) {
|
||||||
|
|
||||||
picker
|
picker
|
||||||
.update(cx, |f, cx| {
|
.update(cx, |f, cx| {
|
||||||
f.delegate.spawn_search(test_path_like("dir"), cx)
|
f.delegate.spawn_search(test_path_position("dir"), cx)
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
cx.read(|cx| {
|
cx.read(|cx| {
|
||||||
|
@ -1854,18 +1854,18 @@ fn init_test(cx: &mut TestAppContext) -> Arc<AppState> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_path_like(test_str: &str) -> PathLikeWithPosition<FileSearchQuery> {
|
fn test_path_position(test_str: &str) -> FileSearchQuery {
|
||||||
PathLikeWithPosition::parse_str(test_str, |normalized_query, path_like_str| {
|
let path_position = PathWithPosition::parse_str(&test_str);
|
||||||
Ok::<_, std::convert::Infallible>(FileSearchQuery {
|
|
||||||
raw_query: normalized_query.to_owned(),
|
FileSearchQuery {
|
||||||
file_query_end: if path_like_str == test_str {
|
raw_query: test_str.to_owned(),
|
||||||
None
|
file_query_end: if path_position.path.to_str().unwrap() == test_str {
|
||||||
} else {
|
None
|
||||||
Some(path_like_str.len())
|
} else {
|
||||||
},
|
Some(path_position.path.to_str().unwrap().len())
|
||||||
})
|
},
|
||||||
})
|
path_position,
|
||||||
.unwrap()
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_find_picker(
|
fn build_find_picker(
|
||||||
|
|
|
@ -39,7 +39,7 @@ use ui::{
|
||||||
RadioWithLabel, Tooltip,
|
RadioWithLabel, Tooltip,
|
||||||
};
|
};
|
||||||
use ui_input::{FieldLabelLayout, TextField};
|
use ui_input::{FieldLabelLayout, TextField};
|
||||||
use util::paths::PathLikeWithPosition;
|
use util::paths::PathWithPosition;
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use workspace::notifications::NotifyResultExt;
|
use workspace::notifications::NotifyResultExt;
|
||||||
use workspace::OpenOptions;
|
use workspace::OpenOptions;
|
||||||
|
@ -991,7 +991,7 @@ impl DevServerProjects {
|
||||||
project
|
project
|
||||||
.paths
|
.paths
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|path| PathLikeWithPosition::from_path(PathBuf::from(path)))
|
.map(|path| PathWithPosition::from_path(PathBuf::from(path)))
|
||||||
.collect(),
|
.collect(),
|
||||||
app_state,
|
app_state,
|
||||||
OpenOptions::default(),
|
OpenOptions::default(),
|
||||||
|
|
|
@ -19,7 +19,7 @@ use ui::{
|
||||||
h_flex, v_flex, FluentBuilder as _, Icon, IconName, IconSize, InteractiveElement, IntoElement,
|
h_flex, v_flex, FluentBuilder as _, Icon, IconName, IconSize, InteractiveElement, IntoElement,
|
||||||
Label, LabelCommon, Styled, StyledExt as _, ViewContext, VisualContext, WindowContext,
|
Label, LabelCommon, Styled, StyledExt as _, ViewContext, VisualContext, WindowContext,
|
||||||
};
|
};
|
||||||
use util::paths::PathLikeWithPosition;
|
use util::paths::PathWithPosition;
|
||||||
use workspace::{AppState, ModalView, Workspace};
|
use workspace::{AppState, ModalView, Workspace};
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
|
@ -345,7 +345,7 @@ pub fn connect_over_ssh(
|
||||||
|
|
||||||
pub async fn open_ssh_project(
|
pub async fn open_ssh_project(
|
||||||
connection_options: SshConnectionOptions,
|
connection_options: SshConnectionOptions,
|
||||||
paths: Vec<PathLikeWithPosition<PathBuf>>,
|
paths: Vec<PathWithPosition>,
|
||||||
app_state: Arc<AppState>,
|
app_state: Arc<AppState>,
|
||||||
_open_options: workspace::OpenOptions,
|
_open_options: workspace::OpenOptions,
|
||||||
cx: &mut AsyncAppContext,
|
cx: &mut AsyncAppContext,
|
||||||
|
@ -398,7 +398,7 @@ pub async fn open_ssh_project(
|
||||||
for path in paths {
|
for path in paths {
|
||||||
project
|
project
|
||||||
.update(cx, |project, cx| {
|
.update(cx, |project, cx| {
|
||||||
project.find_or_create_worktree(&path.path_like, true, cx)
|
project.find_or_create_worktree(&path.path, true, cx)
|
||||||
})?
|
})?
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ use terminal::{
|
||||||
};
|
};
|
||||||
use terminal_element::{is_blank, TerminalElement};
|
use terminal_element::{is_blank, TerminalElement};
|
||||||
use ui::{h_flex, prelude::*, ContextMenu, Icon, IconName, Label, Tooltip};
|
use ui::{h_flex, prelude::*, ContextMenu, Icon, IconName, Label, Tooltip};
|
||||||
use util::{paths::PathLikeWithPosition, ResultExt};
|
use util::{paths::PathWithPosition, ResultExt};
|
||||||
use workspace::{
|
use workspace::{
|
||||||
item::{BreadcrumbText, Item, ItemEvent, SerializableItem, TabContentParams},
|
item::{BreadcrumbText, Item, ItemEvent, SerializableItem, TabContentParams},
|
||||||
notifications::NotifyResultExt,
|
notifications::NotifyResultExt,
|
||||||
|
@ -672,7 +672,7 @@ fn subscribe_for_terminal_events(
|
||||||
.await;
|
.await;
|
||||||
let paths_to_open = valid_files_to_open
|
let paths_to_open = valid_files_to_open
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(p, _)| p.path_like.clone())
|
.map(|(p, _)| p.path.clone())
|
||||||
.collect();
|
.collect();
|
||||||
let opened_items = task_workspace
|
let opened_items = task_workspace
|
||||||
.update(&mut cx, |workspace, cx| {
|
.update(&mut cx, |workspace, cx| {
|
||||||
|
@ -746,7 +746,7 @@ fn possible_open_paths_metadata(
|
||||||
column: Option<u32>,
|
column: Option<u32>,
|
||||||
potential_paths: HashSet<PathBuf>,
|
potential_paths: HashSet<PathBuf>,
|
||||||
cx: &mut ViewContext<TerminalView>,
|
cx: &mut ViewContext<TerminalView>,
|
||||||
) -> Task<Vec<(PathLikeWithPosition<PathBuf>, Metadata)>> {
|
) -> Task<Vec<(PathWithPosition, Metadata)>> {
|
||||||
cx.background_executor().spawn(async move {
|
cx.background_executor().spawn(async move {
|
||||||
let mut paths_with_metadata = Vec::with_capacity(potential_paths.len());
|
let mut paths_with_metadata = Vec::with_capacity(potential_paths.len());
|
||||||
|
|
||||||
|
@ -755,8 +755,8 @@ fn possible_open_paths_metadata(
|
||||||
.map(|potential_path| async {
|
.map(|potential_path| async {
|
||||||
let metadata = fs.metadata(&potential_path).await.ok().flatten();
|
let metadata = fs.metadata(&potential_path).await.ok().flatten();
|
||||||
(
|
(
|
||||||
PathLikeWithPosition {
|
PathWithPosition {
|
||||||
path_like: potential_path,
|
path: potential_path,
|
||||||
row,
|
row,
|
||||||
column,
|
column,
|
||||||
},
|
},
|
||||||
|
@ -781,14 +781,11 @@ fn possible_open_targets(
|
||||||
cwd: &Option<PathBuf>,
|
cwd: &Option<PathBuf>,
|
||||||
maybe_path: &String,
|
maybe_path: &String,
|
||||||
cx: &mut ViewContext<TerminalView>,
|
cx: &mut ViewContext<TerminalView>,
|
||||||
) -> Task<Vec<(PathLikeWithPosition<PathBuf>, Metadata)>> {
|
) -> Task<Vec<(PathWithPosition, Metadata)>> {
|
||||||
let path_like = PathLikeWithPosition::parse_str(maybe_path.as_str(), |_, path_str| {
|
let path_position = PathWithPosition::parse_str(maybe_path.as_str());
|
||||||
Ok::<_, std::convert::Infallible>(Path::new(path_str).to_path_buf())
|
let row = path_position.row;
|
||||||
})
|
let column = path_position.column;
|
||||||
.expect("infallible");
|
let maybe_path = path_position.path;
|
||||||
let row = path_like.row;
|
|
||||||
let column = path_like.column;
|
|
||||||
let maybe_path = path_like.path_like;
|
|
||||||
let potential_abs_paths = if maybe_path.is_absolute() {
|
let potential_abs_paths = if maybe_path.is_absolute() {
|
||||||
HashSet::from_iter([maybe_path])
|
HashSet::from_iter([maybe_path])
|
||||||
} else if maybe_path.starts_with("~") {
|
} else if maybe_path.starts_with("~") {
|
||||||
|
|
|
@ -96,59 +96,56 @@ pub const FILE_ROW_COLUMN_DELIMITER: char = ':';
|
||||||
/// A representation of a path-like string with optional row and column numbers.
|
/// A representation of a path-like string with optional row and column numbers.
|
||||||
/// Matching values example: `te`, `test.rs:22`, `te:22:5`, etc.
|
/// Matching values example: `te`, `test.rs:22`, `te:22:5`, etc.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
|
||||||
pub struct PathLikeWithPosition<P> {
|
pub struct PathWithPosition {
|
||||||
pub path_like: P,
|
pub path: PathBuf,
|
||||||
pub row: Option<u32>,
|
pub row: Option<u32>,
|
||||||
// Absent if row is absent.
|
// Absent if row is absent.
|
||||||
pub column: Option<u32>,
|
pub column: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P> PathLikeWithPosition<P> {
|
impl PathWithPosition {
|
||||||
/// Returns a PathLikeWithPosition from a path.
|
/// Returns a PathWithPosition from a path.
|
||||||
pub fn from_path(path: P) -> Self {
|
pub fn from_path(path: PathBuf) -> Self {
|
||||||
Self {
|
Self {
|
||||||
path_like: path,
|
path,
|
||||||
row: None,
|
row: None,
|
||||||
column: None,
|
column: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Parses a string that possibly has `:row:column` suffix.
|
/// Parses a string that possibly has `:row:column` suffix.
|
||||||
/// Ignores trailing `:`s, so `test.rs:22:` is parsed as `test.rs:22`.
|
/// Ignores trailing `:`s, so `test.rs:22:` is parsed as `test.rs:22`.
|
||||||
/// If any of the row/column component parsing fails, the whole string is then parsed as a path like.
|
/// If the suffix parsing fails, the whole string is parsed as a path.
|
||||||
/// If on Windows, `s` will replace `/` with `\` for compatibility.
|
pub fn parse_str(s: &str) -> Self {
|
||||||
pub fn parse_str<E>(
|
let fallback = |fallback_str| Self {
|
||||||
s: &str,
|
path: Path::new(fallback_str).to_path_buf(),
|
||||||
parse_path_like_str: impl Fn(&str, &str) -> Result<P, E>,
|
row: None,
|
||||||
) -> Result<Self, E> {
|
column: None,
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
let s = &s.replace('/', "\\");
|
|
||||||
|
|
||||||
let fallback = |fallback_str| {
|
|
||||||
Ok(Self {
|
|
||||||
path_like: parse_path_like_str(s, fallback_str)?,
|
|
||||||
row: None,
|
|
||||||
column: None,
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let trimmed = s.trim();
|
let trimmed = s.trim();
|
||||||
|
let path = Path::new(trimmed);
|
||||||
#[cfg(target_os = "windows")]
|
let maybe_file_name_with_row_col = path
|
||||||
{
|
.file_name()
|
||||||
let is_absolute = trimmed.starts_with(r"\\?\");
|
.unwrap_or_default()
|
||||||
if is_absolute {
|
.to_str()
|
||||||
return Self::parse_absolute_path(trimmed, |p| parse_path_like_str(s, p));
|
.unwrap_or_default();
|
||||||
}
|
if maybe_file_name_with_row_col.is_empty() {
|
||||||
|
return fallback(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
match trimmed.split_once(FILE_ROW_COLUMN_DELIMITER) {
|
match maybe_file_name_with_row_col.split_once(FILE_ROW_COLUMN_DELIMITER) {
|
||||||
Some((path_like_str, maybe_row_and_col_str)) => {
|
Some((file_name, maybe_row_and_col_str)) => {
|
||||||
let path_like_str = path_like_str.trim();
|
let file_name = file_name.trim();
|
||||||
let maybe_row_and_col_str = maybe_row_and_col_str.trim();
|
let maybe_row_and_col_str = maybe_row_and_col_str.trim();
|
||||||
if path_like_str.is_empty() {
|
if file_name.is_empty() {
|
||||||
fallback(s)
|
return fallback(s);
|
||||||
} else if maybe_row_and_col_str.is_empty() {
|
}
|
||||||
fallback(path_like_str)
|
|
||||||
|
let suffix_length = maybe_row_and_col_str.len() + 1;
|
||||||
|
let path_without_suffix = &trimmed[..trimmed.len() - suffix_length];
|
||||||
|
|
||||||
|
if maybe_row_and_col_str.is_empty() {
|
||||||
|
fallback(path_without_suffix)
|
||||||
} else {
|
} else {
|
||||||
let (row_parse_result, maybe_col_str) =
|
let (row_parse_result, maybe_col_str) =
|
||||||
match maybe_row_and_col_str.split_once(FILE_ROW_COLUMN_DELIMITER) {
|
match maybe_row_and_col_str.split_once(FILE_ROW_COLUMN_DELIMITER) {
|
||||||
|
@ -158,36 +155,38 @@ impl<P> PathLikeWithPosition<P> {
|
||||||
None => (maybe_row_and_col_str.parse::<u32>(), ""),
|
None => (maybe_row_and_col_str.parse::<u32>(), ""),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let path = Path::new(path_without_suffix).to_path_buf();
|
||||||
|
|
||||||
match row_parse_result {
|
match row_parse_result {
|
||||||
Ok(row) => {
|
Ok(row) => {
|
||||||
if maybe_col_str.is_empty() {
|
if maybe_col_str.is_empty() {
|
||||||
Ok(Self {
|
Self {
|
||||||
path_like: parse_path_like_str(s, path_like_str)?,
|
path,
|
||||||
row: Some(row),
|
row: Some(row),
|
||||||
column: None,
|
column: None,
|
||||||
})
|
}
|
||||||
} else {
|
} else {
|
||||||
let (maybe_col_str, _) =
|
let (maybe_col_str, _) =
|
||||||
maybe_col_str.split_once(':').unwrap_or((maybe_col_str, ""));
|
maybe_col_str.split_once(':').unwrap_or((maybe_col_str, ""));
|
||||||
match maybe_col_str.parse::<u32>() {
|
match maybe_col_str.parse::<u32>() {
|
||||||
Ok(col) => Ok(Self {
|
Ok(col) => Self {
|
||||||
path_like: parse_path_like_str(s, path_like_str)?,
|
path,
|
||||||
row: Some(row),
|
row: Some(row),
|
||||||
column: Some(col),
|
column: Some(col),
|
||||||
}),
|
},
|
||||||
Err(_) => Ok(Self {
|
Err(_) => Self {
|
||||||
path_like: parse_path_like_str(s, path_like_str)?,
|
path,
|
||||||
row: Some(row),
|
row: Some(row),
|
||||||
column: None,
|
column: None,
|
||||||
}),
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(_) => Ok(Self {
|
Err(_) => Self {
|
||||||
path_like: parse_path_like_str(s, path_like_str)?,
|
path,
|
||||||
row: None,
|
row: None,
|
||||||
column: None,
|
column: None,
|
||||||
}),
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -195,79 +194,27 @@ impl<P> PathLikeWithPosition<P> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This helper function is used for parsing absolute paths on Windows. It exists because absolute paths on Windows are quite different from other platforms. See [this page](https://learn.microsoft.com/en-us/dotnet/standard/io/file-path-formats#dos-device-paths) for more information.
|
pub fn map_path<E>(
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
fn parse_absolute_path<E>(
|
|
||||||
s: &str,
|
|
||||||
parse_path_like_str: impl Fn(&str) -> Result<P, E>,
|
|
||||||
) -> Result<Self, E> {
|
|
||||||
let fallback = |fallback_str| {
|
|
||||||
Ok(Self {
|
|
||||||
path_like: parse_path_like_str(fallback_str)?,
|
|
||||||
row: None,
|
|
||||||
column: None,
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut iterator = s.split(FILE_ROW_COLUMN_DELIMITER);
|
|
||||||
|
|
||||||
let drive_prefix = iterator.next().unwrap_or_default();
|
|
||||||
let file_path = iterator.next().unwrap_or_default();
|
|
||||||
|
|
||||||
// TODO: How to handle drives without a letter? UNC paths?
|
|
||||||
let complete_path = drive_prefix.replace("\\\\?\\", "") + ":" + &file_path;
|
|
||||||
|
|
||||||
if let Some(row_str) = iterator.next() {
|
|
||||||
if let Some(column_str) = iterator.next() {
|
|
||||||
match row_str.parse::<u32>() {
|
|
||||||
Ok(row) => match column_str.parse::<u32>() {
|
|
||||||
Ok(col) => {
|
|
||||||
return Ok(Self {
|
|
||||||
path_like: parse_path_like_str(&complete_path)?,
|
|
||||||
row: Some(row),
|
|
||||||
column: Some(col),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(_) => {
|
|
||||||
return Ok(Self {
|
|
||||||
path_like: parse_path_like_str(&complete_path)?,
|
|
||||||
row: Some(row),
|
|
||||||
column: None,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
Err(_) => {
|
|
||||||
return fallback(&complete_path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fallback(&complete_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn map_path_like<P2, E>(
|
|
||||||
self,
|
self,
|
||||||
mapping: impl FnOnce(P) -> Result<P2, E>,
|
mapping: impl FnOnce(PathBuf) -> Result<PathBuf, E>,
|
||||||
) -> Result<PathLikeWithPosition<P2>, E> {
|
) -> Result<PathWithPosition, E> {
|
||||||
Ok(PathLikeWithPosition {
|
Ok(PathWithPosition {
|
||||||
path_like: mapping(self.path_like)?,
|
path: mapping(self.path)?,
|
||||||
row: self.row,
|
row: self.row,
|
||||||
column: self.column,
|
column: self.column,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_string(&self, path_like_to_string: impl Fn(&P) -> String) -> String {
|
pub fn to_string(&self, path_to_string: impl Fn(&PathBuf) -> String) -> String {
|
||||||
let path_like_string = path_like_to_string(&self.path_like);
|
let path_string = path_to_string(&self.path);
|
||||||
if let Some(row) = self.row {
|
if let Some(row) = self.row {
|
||||||
if let Some(column) = self.column {
|
if let Some(column) = self.column {
|
||||||
format!("{path_like_string}:{row}:{column}")
|
format!("{path_string}:{row}:{column}")
|
||||||
} else {
|
} else {
|
||||||
format!("{path_like_string}:{row}")
|
format!("{path_string}:{row}")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
path_like_string
|
path_string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -335,38 +282,29 @@ impl PathMatcher {
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
type TestPath = PathLikeWithPosition<(String, String)>;
|
|
||||||
|
|
||||||
fn parse_str(s: &str) -> TestPath {
|
|
||||||
TestPath::parse_str(s, |normalized, s| {
|
|
||||||
Ok::<_, std::convert::Infallible>((normalized.to_string(), s.to_string()))
|
|
||||||
})
|
|
||||||
.expect("infallible")
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn path_with_position_parsing_positive() {
|
fn path_with_position_parsing_positive() {
|
||||||
let input_and_expected = [
|
let input_and_expected = [
|
||||||
(
|
(
|
||||||
"test_file.rs",
|
"test_file.rs",
|
||||||
PathLikeWithPosition {
|
PathWithPosition {
|
||||||
path_like: ("test_file.rs".to_string(), "test_file.rs".to_string()),
|
path: PathBuf::from("test_file.rs"),
|
||||||
row: None,
|
row: None,
|
||||||
column: None,
|
column: None,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"test_file.rs:1",
|
"test_file.rs:1",
|
||||||
PathLikeWithPosition {
|
PathWithPosition {
|
||||||
path_like: ("test_file.rs:1".to_string(), "test_file.rs".to_string()),
|
path: PathBuf::from("test_file.rs"),
|
||||||
row: Some(1),
|
row: Some(1),
|
||||||
column: None,
|
column: None,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"test_file.rs:1:2",
|
"test_file.rs:1:2",
|
||||||
PathLikeWithPosition {
|
PathWithPosition {
|
||||||
path_like: ("test_file.rs:1:2".to_string(), "test_file.rs".to_string()),
|
path: PathBuf::from("test_file.rs"),
|
||||||
row: Some(1),
|
row: Some(1),
|
||||||
column: Some(2),
|
column: Some(2),
|
||||||
},
|
},
|
||||||
|
@ -374,7 +312,7 @@ mod tests {
|
||||||
];
|
];
|
||||||
|
|
||||||
for (input, expected) in input_and_expected {
|
for (input, expected) in input_and_expected {
|
||||||
let actual = parse_str(input);
|
let actual = PathWithPosition::parse_str(input);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
actual, expected,
|
actual, expected,
|
||||||
"For positive case input str '{input}', got a parse mismatch"
|
"For positive case input str '{input}', got a parse mismatch"
|
||||||
|
@ -394,11 +332,11 @@ mod tests {
|
||||||
("test_file.rs:1::2", Some(1), None),
|
("test_file.rs:1::2", Some(1), None),
|
||||||
("test_file.rs:1:2:3", Some(1), Some(2)),
|
("test_file.rs:1:2:3", Some(1), Some(2)),
|
||||||
] {
|
] {
|
||||||
let actual = parse_str(input);
|
let actual = PathWithPosition::parse_str(input);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
actual,
|
actual,
|
||||||
PathLikeWithPosition {
|
PathWithPosition {
|
||||||
path_like: (input.to_string(), "test_file.rs".to_string()),
|
path: PathBuf::from("test_file.rs"),
|
||||||
row,
|
row,
|
||||||
column,
|
column,
|
||||||
},
|
},
|
||||||
|
@ -414,27 +352,24 @@ mod tests {
|
||||||
let input_and_expected = [
|
let input_and_expected = [
|
||||||
(
|
(
|
||||||
"test_file.rs:",
|
"test_file.rs:",
|
||||||
PathLikeWithPosition {
|
PathWithPosition {
|
||||||
path_like: ("test_file.rs:".to_string(), "test_file.rs".to_string()),
|
path: PathBuf::from("test_file.rs"),
|
||||||
row: None,
|
row: None,
|
||||||
column: None,
|
column: None,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"test_file.rs:1:",
|
"test_file.rs:1:",
|
||||||
PathLikeWithPosition {
|
PathWithPosition {
|
||||||
path_like: ("test_file.rs:1:".to_string(), "test_file.rs".to_string()),
|
path: PathBuf::from("test_file.rs"),
|
||||||
row: Some(1),
|
row: Some(1),
|
||||||
column: None,
|
column: None,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"crates/file_finder/src/file_finder.rs:1902:13:",
|
"crates/file_finder/src/file_finder.rs:1902:13:",
|
||||||
PathLikeWithPosition {
|
PathWithPosition {
|
||||||
path_like: (
|
path: PathBuf::from("crates/file_finder/src/file_finder.rs"),
|
||||||
"crates/file_finder/src/file_finder.rs:1902:13:".to_string(),
|
|
||||||
"crates/file_finder/src/file_finder.rs".to_string(),
|
|
||||||
),
|
|
||||||
row: Some(1902),
|
row: Some(1902),
|
||||||
column: Some(13),
|
column: Some(13),
|
||||||
},
|
},
|
||||||
|
@ -445,71 +380,64 @@ mod tests {
|
||||||
let input_and_expected = [
|
let input_and_expected = [
|
||||||
(
|
(
|
||||||
"test_file.rs:",
|
"test_file.rs:",
|
||||||
PathLikeWithPosition {
|
PathWithPosition {
|
||||||
path_like: ("test_file.rs:".to_string(), "test_file.rs".to_string()),
|
path: PathBuf::from("test_file.rs"),
|
||||||
row: None,
|
row: None,
|
||||||
column: None,
|
column: None,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"test_file.rs:1:",
|
"test_file.rs:1:",
|
||||||
PathLikeWithPosition {
|
PathWithPosition {
|
||||||
path_like: ("test_file.rs:1:".to_string(), "test_file.rs".to_string()),
|
path: PathBuf::from("test_file.rs"),
|
||||||
row: Some(1),
|
row: Some(1),
|
||||||
column: None,
|
column: None,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"\\\\?\\C:\\Users\\someone\\test_file.rs:1902:13:",
|
"\\\\?\\C:\\Users\\someone\\test_file.rs:1902:13:",
|
||||||
PathLikeWithPosition {
|
PathWithPosition {
|
||||||
path_like: (
|
path: PathBuf::from("\\\\?\\C:\\Users\\someone\\test_file.rs"),
|
||||||
"\\\\?\\C:\\Users\\someone\\test_file.rs:1902:13:".to_string(),
|
|
||||||
"C:\\Users\\someone\\test_file.rs".to_string(),
|
|
||||||
),
|
|
||||||
row: Some(1902),
|
row: Some(1902),
|
||||||
column: Some(13),
|
column: Some(13),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"\\\\?\\C:\\Users\\someone\\test_file.rs:1902:13:15:",
|
"\\\\?\\C:\\Users\\someone\\test_file.rs:1902:13:15:",
|
||||||
PathLikeWithPosition {
|
PathWithPosition {
|
||||||
path_like: (
|
path: PathBuf::from("\\\\?\\C:\\Users\\someone\\test_file.rs"),
|
||||||
"\\\\?\\C:\\Users\\someone\\test_file.rs:1902:13:15:".to_string(),
|
|
||||||
"C:\\Users\\someone\\test_file.rs".to_string(),
|
|
||||||
),
|
|
||||||
row: Some(1902),
|
row: Some(1902),
|
||||||
column: Some(13),
|
column: Some(13),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"\\\\?\\C:\\Users\\someone\\test_file.rs:1902:::15:",
|
"\\\\?\\C:\\Users\\someone\\test_file.rs:1902:::15:",
|
||||||
PathLikeWithPosition {
|
PathWithPosition {
|
||||||
path_like: (
|
path: PathBuf::from("\\\\?\\C:\\Users\\someone\\test_file.rs"),
|
||||||
"\\\\?\\C:\\Users\\someone\\test_file.rs:1902:::15:".to_string(),
|
|
||||||
"C:\\Users\\someone\\test_file.rs".to_string(),
|
|
||||||
),
|
|
||||||
row: Some(1902),
|
row: Some(1902),
|
||||||
column: None,
|
column: None,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
"C:\\Users\\someone\\test_file.rs:1902:13:",
|
||||||
|
PathWithPosition {
|
||||||
|
path: PathBuf::from("C:\\Users\\someone\\test_file.rs"),
|
||||||
|
row: Some(1902),
|
||||||
|
column: Some(13),
|
||||||
|
},
|
||||||
|
),
|
||||||
(
|
(
|
||||||
"crates/utils/paths.rs",
|
"crates/utils/paths.rs",
|
||||||
PathLikeWithPosition {
|
PathWithPosition {
|
||||||
path_like: (
|
path: PathBuf::from("crates\\utils\\paths.rs"),
|
||||||
"crates\\utils\\paths.rs".to_string(),
|
|
||||||
"crates\\utils\\paths.rs".to_string(),
|
|
||||||
),
|
|
||||||
row: None,
|
row: None,
|
||||||
column: None,
|
column: None,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"crates/utils/paths.rs:101",
|
"crates/utils/paths.rs:101",
|
||||||
PathLikeWithPosition {
|
PathWithPosition {
|
||||||
path_like: (
|
path: PathBuf::from("crates\\utils\\paths.rs"),
|
||||||
"crates\\utils\\paths.rs:101".to_string(),
|
|
||||||
"crates\\utils\\paths.rs".to_string(),
|
|
||||||
),
|
|
||||||
row: Some(101),
|
row: Some(101),
|
||||||
column: None,
|
column: None,
|
||||||
},
|
},
|
||||||
|
@ -517,7 +445,7 @@ mod tests {
|
||||||
];
|
];
|
||||||
|
|
||||||
for (input, expected) in input_and_expected {
|
for (input, expected) in input_and_expected {
|
||||||
let actual = parse_str(input);
|
let actual = PathWithPosition::parse_str(input);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
actual, expected,
|
actual, expected,
|
||||||
"For special case input str '{input}', got a parse mismatch"
|
"For special case input str '{input}', got a parse mismatch"
|
||||||
|
|
|
@ -14,12 +14,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 remote::SshConnectionOptions;
|
use remote::SshConnectionOptions;
|
||||||
use std::path::Path;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::{process, thread};
|
use std::{process, thread};
|
||||||
use util::paths::PathLikeWithPosition;
|
use util::paths::PathWithPosition;
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use welcome::{show_welcome_view, FIRST_OPEN};
|
use welcome::{show_welcome_view, FIRST_OPEN};
|
||||||
use workspace::item::ItemHandle;
|
use workspace::item::ItemHandle;
|
||||||
|
@ -28,7 +26,7 @@ use workspace::{AppState, Workspace};
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
pub struct OpenRequest {
|
pub struct OpenRequest {
|
||||||
pub cli_connection: Option<(mpsc::Receiver<CliRequest>, IpcSender<CliResponse>)>,
|
pub cli_connection: Option<(mpsc::Receiver<CliRequest>, IpcSender<CliResponse>)>,
|
||||||
pub open_paths: Vec<PathLikeWithPosition<PathBuf>>,
|
pub open_paths: Vec<PathWithPosition>,
|
||||||
pub open_channel_notes: Vec<(u64, Option<String>)>,
|
pub open_channel_notes: Vec<(u64, Option<String>)>,
|
||||||
pub join_channel: Option<u64>,
|
pub join_channel: Option<u64>,
|
||||||
pub ssh_connection: Option<SshConnectionOptions>,
|
pub ssh_connection: Option<SshConnectionOptions>,
|
||||||
|
@ -58,11 +56,8 @@ impl OpenRequest {
|
||||||
|
|
||||||
fn parse_file_path(&mut self, file: &str) {
|
fn parse_file_path(&mut self, file: &str) {
|
||||||
if let Some(decoded) = urlencoding::decode(file).log_err() {
|
if let Some(decoded) = urlencoding::decode(file).log_err() {
|
||||||
if let Some(path_buf) =
|
let path_buf = PathWithPosition::parse_str(&decoded);
|
||||||
PathLikeWithPosition::parse_str(&decoded, |_, s| PathBuf::try_from(s)).log_err()
|
self.open_paths.push(path_buf)
|
||||||
{
|
|
||||||
self.open_paths.push(path_buf)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -193,7 +188,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_positions: &Vec<PathWithPosition>,
|
||||||
app_state: Arc<AppState>,
|
app_state: Arc<AppState>,
|
||||||
open_options: workspace::OpenOptions,
|
open_options: workspace::OpenOptions,
|
||||||
cx: &mut AsyncAppContext,
|
cx: &mut AsyncAppContext,
|
||||||
|
@ -203,10 +198,10 @@ pub async fn open_paths_with_positions(
|
||||||
)> {
|
)> {
|
||||||
let mut caret_positions = HashMap::default();
|
let mut caret_positions = HashMap::default();
|
||||||
|
|
||||||
let paths = path_likes
|
let paths = path_positions
|
||||||
.iter()
|
.iter()
|
||||||
.map(|path_with_position| {
|
.map(|path_with_position| {
|
||||||
let path = path_with_position.path_like.clone();
|
let path = path_with_position.path.clone();
|
||||||
if let Some(row) = path_with_position.row {
|
if let Some(row) = path_with_position.row {
|
||||||
if path.is_file() {
|
if path.is_file() {
|
||||||
let row = row.saturating_sub(1);
|
let row = row.saturating_sub(1);
|
||||||
|
@ -364,8 +359,8 @@ async fn open_workspaces(
|
||||||
location
|
location
|
||||||
.paths()
|
.paths()
|
||||||
.iter()
|
.iter()
|
||||||
.map(|path| PathLikeWithPosition {
|
.map(|path| PathWithPosition {
|
||||||
path_like: path.clone(),
|
path: path.clone(),
|
||||||
row: None,
|
row: None,
|
||||||
column: None,
|
column: None,
|
||||||
})
|
})
|
||||||
|
@ -380,10 +375,7 @@ async fn open_workspaces(
|
||||||
let paths_with_position = paths
|
let paths_with_position = paths
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|path_with_position_string| {
|
.map(|path_with_position_string| {
|
||||||
PathLikeWithPosition::parse_str(&path_with_position_string, |_, path_str| {
|
PathWithPosition::parse_str(&path_with_position_string)
|
||||||
Ok::<_, std::convert::Infallible>(Path::new(path_str).to_path_buf())
|
|
||||||
})
|
|
||||||
.expect("Infallible")
|
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
vec![paths_with_position]
|
vec![paths_with_position]
|
||||||
|
@ -434,7 +426,7 @@ async fn open_workspaces(
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn open_workspace(
|
async fn open_workspace(
|
||||||
workspace_paths: Vec<PathLikeWithPosition<PathBuf>>,
|
workspace_paths: Vec<PathWithPosition>,
|
||||||
open_new_workspace: Option<bool>,
|
open_new_workspace: Option<bool>,
|
||||||
wait: bool,
|
wait: bool,
|
||||||
responses: &IpcSender<CliResponse>,
|
responses: &IpcSender<CliResponse>,
|
||||||
|
@ -542,7 +534,7 @@ mod tests {
|
||||||
use editor::Editor;
|
use editor::Editor;
|
||||||
use gpui::TestAppContext;
|
use gpui::TestAppContext;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use util::paths::PathLikeWithPosition;
|
use util::paths::PathWithPosition;
|
||||||
use workspace::{AppState, Workspace};
|
use workspace::{AppState, Workspace};
|
||||||
|
|
||||||
use crate::zed::{open_listener::open_workspace, tests::init_test};
|
use crate::zed::{open_listener::open_workspace, tests::init_test};
|
||||||
|
@ -656,9 +648,9 @@ mod tests {
|
||||||
) {
|
) {
|
||||||
let (response_tx, _) = ipc::channel::<CliResponse>().unwrap();
|
let (response_tx, _) = ipc::channel::<CliResponse>().unwrap();
|
||||||
|
|
||||||
let path_like = PathBuf::from(path);
|
let path = PathBuf::from(path);
|
||||||
let workspace_paths = vec![PathLikeWithPosition {
|
let workspace_paths = vec![PathWithPosition {
|
||||||
path_like,
|
path,
|
||||||
row: None,
|
row: None,
|
||||||
column: None,
|
column: None,
|
||||||
}];
|
}];
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue