mirror of
https://github.com/zed-industries/zed.git
synced 2024-11-08 07:35:01 +03:00
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
@ -5,14 +5,13 @@ use clap::Parser;
|
||||
use cli::{ipc::IpcOneShotServer, CliRequest, CliResponse, IpcHandshake};
|
||||
use parking_lot::Mutex;
|
||||
use std::{
|
||||
convert::Infallible,
|
||||
env, fs, io,
|
||||
path::{Path, PathBuf},
|
||||
process::ExitStatus,
|
||||
sync::Arc,
|
||||
thread::{self, JoinHandle},
|
||||
};
|
||||
use util::paths::PathLikeWithPosition;
|
||||
use util::paths::PathWithPosition;
|
||||
|
||||
struct Detect;
|
||||
|
||||
@ -54,13 +53,10 @@ struct Args {
|
||||
}
|
||||
|
||||
fn parse_path_with_position(argument_str: &str) -> Result<String, std::io::Error> {
|
||||
let path_like = PathLikeWithPosition::parse_str::<Infallible>(argument_str, |_, path_str| {
|
||||
Ok(Path::new(path_str).to_path_buf())
|
||||
})
|
||||
.unwrap();
|
||||
let path = PathWithPosition::parse_str(argument_str);
|
||||
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),
|
||||
Err(e) => {
|
||||
if let Some(mut parent) = path.parent() {
|
||||
|
@ -28,7 +28,7 @@ use std::{
|
||||
};
|
||||
use text::Point;
|
||||
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};
|
||||
|
||||
actions!(file_finder, [SelectPrev]);
|
||||
@ -158,7 +158,7 @@ pub struct FileFinderDelegate {
|
||||
search_count: usize,
|
||||
latest_search_id: usize,
|
||||
latest_search_did_cancel: bool,
|
||||
latest_search_query: Option<PathLikeWithPosition<FileSearchQuery>>,
|
||||
latest_search_query: Option<FileSearchQuery>,
|
||||
currently_opened_path: Option<FoundPath>,
|
||||
matches: Matches,
|
||||
selected_index: usize,
|
||||
@ -226,7 +226,7 @@ impl Matches {
|
||||
&'a mut self,
|
||||
history_items: impl IntoIterator<Item = &'a FoundPath> + Clone,
|
||||
currently_opened: Option<&'a FoundPath>,
|
||||
query: Option<&PathLikeWithPosition<FileSearchQuery>>,
|
||||
query: Option<&FileSearchQuery>,
|
||||
new_search_matches: impl Iterator<Item = ProjectPanelOrdMatch>,
|
||||
extend_old_matches: bool,
|
||||
) {
|
||||
@ -303,7 +303,7 @@ impl Matches {
|
||||
fn matching_history_item_paths<'a>(
|
||||
history_items: impl IntoIterator<Item = &'a FoundPath>,
|
||||
currently_opened: Option<&'a FoundPath>,
|
||||
query: Option<&PathLikeWithPosition<FileSearchQuery>>,
|
||||
query: Option<&FileSearchQuery>,
|
||||
) -> HashMap<Arc<Path>, Option<ProjectPanelOrdMatch>> {
|
||||
let Some(query) = query else {
|
||||
return history_items
|
||||
@ -351,7 +351,7 @@ fn matching_history_item_paths<'a>(
|
||||
fuzzy::match_fixed_path_set(
|
||||
candidates,
|
||||
worktree.to_usize(),
|
||||
query.path_like.path_query(),
|
||||
query.path_query(),
|
||||
false,
|
||||
max_results,
|
||||
)
|
||||
@ -400,6 +400,7 @@ pub enum Event {
|
||||
struct FileSearchQuery {
|
||||
raw_query: String,
|
||||
file_query_end: Option<usize>,
|
||||
path_position: PathWithPosition,
|
||||
}
|
||||
|
||||
impl FileSearchQuery {
|
||||
@ -456,7 +457,7 @@ impl FileFinderDelegate {
|
||||
|
||||
fn spawn_search(
|
||||
&mut self,
|
||||
query: PathLikeWithPosition<FileSearchQuery>,
|
||||
query: FileSearchQuery,
|
||||
cx: &mut ViewContext<Picker<Self>>,
|
||||
) -> Task<()> {
|
||||
let relative_to = self
|
||||
@ -491,7 +492,7 @@ impl FileFinderDelegate {
|
||||
cx.spawn(|picker, mut cx| async move {
|
||||
let matches = fuzzy::match_path_sets(
|
||||
candidate_sets.as_slice(),
|
||||
query.path_like.path_query(),
|
||||
query.path_query(),
|
||||
relative_to,
|
||||
false,
|
||||
100,
|
||||
@ -516,18 +517,18 @@ impl FileFinderDelegate {
|
||||
&mut self,
|
||||
search_id: usize,
|
||||
did_cancel: bool,
|
||||
query: PathLikeWithPosition<FileSearchQuery>,
|
||||
query: FileSearchQuery,
|
||||
matches: impl IntoIterator<Item = ProjectPanelOrdMatch>,
|
||||
cx: &mut ViewContext<Picker<Self>>,
|
||||
) {
|
||||
if search_id >= self.latest_search_id {
|
||||
self.latest_search_id = search_id;
|
||||
let extend_old_matches = self.latest_search_did_cancel
|
||||
&& Some(query.path_like.path_query())
|
||||
&& Some(query.path_query())
|
||||
== self
|
||||
.latest_search_query
|
||||
.as_ref()
|
||||
.map(|query| query.path_like.path_query());
|
||||
.map(|query| query.path_query());
|
||||
self.matches.push_new_matches(
|
||||
&self.history_items,
|
||||
self.currently_opened_path.as_ref(),
|
||||
@ -658,7 +659,7 @@ impl FileFinderDelegate {
|
||||
|
||||
fn lookup_absolute_path(
|
||||
&self,
|
||||
query: PathLikeWithPosition<FileSearchQuery>,
|
||||
query: FileSearchQuery,
|
||||
cx: &mut ViewContext<'_, Picker<Self>>,
|
||||
) -> Task<()> {
|
||||
cx.spawn(|picker, mut cx| async move {
|
||||
@ -672,7 +673,7 @@ impl FileFinderDelegate {
|
||||
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();
|
||||
match fs.metadata(query_path).await.log_err() {
|
||||
Some(Some(_metadata)) => {
|
||||
@ -796,20 +797,20 @@ impl PickerDelegate for FileFinderDelegate {
|
||||
cx.notify();
|
||||
Task::ready(())
|
||||
} else {
|
||||
let 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");
|
||||
let path_position = PathWithPosition::parse_str(&raw_query);
|
||||
|
||||
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)
|
||||
} else {
|
||||
self.spawn_search(query, cx)
|
||||
@ -898,12 +899,12 @@ impl PickerDelegate for FileFinderDelegate {
|
||||
let row = self
|
||||
.latest_search_query
|
||||
.as_ref()
|
||||
.and_then(|query| query.row)
|
||||
.and_then(|query| query.path_position.row)
|
||||
.map(|row| row.saturating_sub(1));
|
||||
let col = self
|
||||
.latest_search_query
|
||||
.as_ref()
|
||||
.and_then(|query| query.column)
|
||||
.and_then(|query| query.path_position.column)
|
||||
.unwrap_or(0)
|
||||
.saturating_sub(1);
|
||||
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
|
||||
.as_ref()
|
||||
.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!(
|
||||
latest_search_query.path_like.file_query_end,
|
||||
Some(file_query.len())
|
||||
latest_search_query.path_position.column,
|
||||
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);
|
||||
@ -301,13 +301,13 @@ async fn test_row_column_numbers_query_outside_file(cx: &mut TestAppContext) {
|
||||
.latest_search_query
|
||||
.as_ref()
|
||||
.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!(
|
||||
latest_search_query.path_like.file_query_end,
|
||||
Some(file_query.len())
|
||||
latest_search_query.path_position.column,
|
||||
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);
|
||||
@ -357,7 +357,7 @@ async fn test_matching_cancellation(cx: &mut TestAppContext) {
|
||||
|
||||
let (picker, _, cx) = build_find_picker(project, cx);
|
||||
|
||||
let query = test_path_like("hi");
|
||||
let query = test_path_position("hi");
|
||||
picker
|
||||
.update(cx, |picker, cx| {
|
||||
picker.delegate.spawn_search(query.clone(), cx)
|
||||
@ -450,7 +450,7 @@ async fn test_ignored_root(cx: &mut TestAppContext) {
|
||||
|
||||
picker
|
||||
.update(cx, |picker, cx| {
|
||||
picker.delegate.spawn_search(test_path_like("hi"), cx)
|
||||
picker.delegate.spawn_search(test_path_position("hi"), cx)
|
||||
})
|
||||
.await;
|
||||
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.
|
||||
picker
|
||||
.update(cx, |picker, cx| {
|
||||
picker.delegate.spawn_search(test_path_like("thf"), cx)
|
||||
picker.delegate.spawn_search(test_path_position("thf"), cx)
|
||||
})
|
||||
.await;
|
||||
cx.read(|cx| {
|
||||
@ -499,7 +499,7 @@ async fn test_single_file_worktrees(cx: &mut TestAppContext) {
|
||||
// not match anything.
|
||||
picker
|
||||
.update(cx, |f, cx| {
|
||||
f.delegate.spawn_search(test_path_like("thf/"), cx)
|
||||
f.delegate.spawn_search(test_path_position("thf/"), cx)
|
||||
})
|
||||
.await;
|
||||
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);
|
||||
finder
|
||||
.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;
|
||||
|
||||
@ -581,7 +581,7 @@ async fn test_search_worktree_without_files(cx: &mut TestAppContext) {
|
||||
|
||||
picker
|
||||
.update(cx, |f, cx| {
|
||||
f.delegate.spawn_search(test_path_like("dir"), cx)
|
||||
f.delegate.spawn_search(test_path_position("dir"), cx)
|
||||
})
|
||||
.await;
|
||||
cx.read(|cx| {
|
||||
@ -1854,18 +1854,18 @@ fn init_test(cx: &mut TestAppContext) -> Arc<AppState> {
|
||||
})
|
||||
}
|
||||
|
||||
fn test_path_like(test_str: &str) -> PathLikeWithPosition<FileSearchQuery> {
|
||||
PathLikeWithPosition::parse_str(test_str, |normalized_query, path_like_str| {
|
||||
Ok::<_, std::convert::Infallible>(FileSearchQuery {
|
||||
raw_query: normalized_query.to_owned(),
|
||||
file_query_end: if path_like_str == test_str {
|
||||
None
|
||||
} else {
|
||||
Some(path_like_str.len())
|
||||
},
|
||||
})
|
||||
})
|
||||
.unwrap()
|
||||
fn test_path_position(test_str: &str) -> FileSearchQuery {
|
||||
let path_position = PathWithPosition::parse_str(&test_str);
|
||||
|
||||
FileSearchQuery {
|
||||
raw_query: test_str.to_owned(),
|
||||
file_query_end: if path_position.path.to_str().unwrap() == test_str {
|
||||
None
|
||||
} else {
|
||||
Some(path_position.path.to_str().unwrap().len())
|
||||
},
|
||||
path_position,
|
||||
}
|
||||
}
|
||||
|
||||
fn build_find_picker(
|
||||
|
@ -39,7 +39,7 @@ use ui::{
|
||||
RadioWithLabel, Tooltip,
|
||||
};
|
||||
use ui_input::{FieldLabelLayout, TextField};
|
||||
use util::paths::PathLikeWithPosition;
|
||||
use util::paths::PathWithPosition;
|
||||
use util::ResultExt;
|
||||
use workspace::notifications::NotifyResultExt;
|
||||
use workspace::OpenOptions;
|
||||
@ -991,7 +991,7 @@ impl DevServerProjects {
|
||||
project
|
||||
.paths
|
||||
.into_iter()
|
||||
.map(|path| PathLikeWithPosition::from_path(PathBuf::from(path)))
|
||||
.map(|path| PathWithPosition::from_path(PathBuf::from(path)))
|
||||
.collect(),
|
||||
app_state,
|
||||
OpenOptions::default(),
|
||||
|
@ -19,7 +19,7 @@ use ui::{
|
||||
h_flex, v_flex, FluentBuilder as _, Icon, IconName, IconSize, InteractiveElement, IntoElement,
|
||||
Label, LabelCommon, Styled, StyledExt as _, ViewContext, VisualContext, WindowContext,
|
||||
};
|
||||
use util::paths::PathLikeWithPosition;
|
||||
use util::paths::PathWithPosition;
|
||||
use workspace::{AppState, ModalView, Workspace};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@ -345,7 +345,7 @@ pub fn connect_over_ssh(
|
||||
|
||||
pub async fn open_ssh_project(
|
||||
connection_options: SshConnectionOptions,
|
||||
paths: Vec<PathLikeWithPosition<PathBuf>>,
|
||||
paths: Vec<PathWithPosition>,
|
||||
app_state: Arc<AppState>,
|
||||
_open_options: workspace::OpenOptions,
|
||||
cx: &mut AsyncAppContext,
|
||||
@ -398,7 +398,7 @@ pub async fn open_ssh_project(
|
||||
for path in paths {
|
||||
project
|
||||
.update(cx, |project, cx| {
|
||||
project.find_or_create_worktree(&path.path_like, true, cx)
|
||||
project.find_or_create_worktree(&path.path, true, cx)
|
||||
})?
|
||||
.await?;
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ use terminal::{
|
||||
};
|
||||
use terminal_element::{is_blank, TerminalElement};
|
||||
use ui::{h_flex, prelude::*, ContextMenu, Icon, IconName, Label, Tooltip};
|
||||
use util::{paths::PathLikeWithPosition, ResultExt};
|
||||
use util::{paths::PathWithPosition, ResultExt};
|
||||
use workspace::{
|
||||
item::{BreadcrumbText, Item, ItemEvent, SerializableItem, TabContentParams},
|
||||
notifications::NotifyResultExt,
|
||||
@ -672,7 +672,7 @@ fn subscribe_for_terminal_events(
|
||||
.await;
|
||||
let paths_to_open = valid_files_to_open
|
||||
.iter()
|
||||
.map(|(p, _)| p.path_like.clone())
|
||||
.map(|(p, _)| p.path.clone())
|
||||
.collect();
|
||||
let opened_items = task_workspace
|
||||
.update(&mut cx, |workspace, cx| {
|
||||
@ -746,7 +746,7 @@ fn possible_open_paths_metadata(
|
||||
column: Option<u32>,
|
||||
potential_paths: HashSet<PathBuf>,
|
||||
cx: &mut ViewContext<TerminalView>,
|
||||
) -> Task<Vec<(PathLikeWithPosition<PathBuf>, Metadata)>> {
|
||||
) -> Task<Vec<(PathWithPosition, Metadata)>> {
|
||||
cx.background_executor().spawn(async move {
|
||||
let mut paths_with_metadata = Vec::with_capacity(potential_paths.len());
|
||||
|
||||
@ -755,8 +755,8 @@ fn possible_open_paths_metadata(
|
||||
.map(|potential_path| async {
|
||||
let metadata = fs.metadata(&potential_path).await.ok().flatten();
|
||||
(
|
||||
PathLikeWithPosition {
|
||||
path_like: potential_path,
|
||||
PathWithPosition {
|
||||
path: potential_path,
|
||||
row,
|
||||
column,
|
||||
},
|
||||
@ -781,14 +781,11 @@ fn possible_open_targets(
|
||||
cwd: &Option<PathBuf>,
|
||||
maybe_path: &String,
|
||||
cx: &mut ViewContext<TerminalView>,
|
||||
) -> Task<Vec<(PathLikeWithPosition<PathBuf>, Metadata)>> {
|
||||
let path_like = PathLikeWithPosition::parse_str(maybe_path.as_str(), |_, path_str| {
|
||||
Ok::<_, std::convert::Infallible>(Path::new(path_str).to_path_buf())
|
||||
})
|
||||
.expect("infallible");
|
||||
let row = path_like.row;
|
||||
let column = path_like.column;
|
||||
let maybe_path = path_like.path_like;
|
||||
) -> Task<Vec<(PathWithPosition, Metadata)>> {
|
||||
let path_position = PathWithPosition::parse_str(maybe_path.as_str());
|
||||
let row = path_position.row;
|
||||
let column = path_position.column;
|
||||
let maybe_path = path_position.path;
|
||||
let potential_abs_paths = if maybe_path.is_absolute() {
|
||||
HashSet::from_iter([maybe_path])
|
||||
} 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.
|
||||
/// Matching values example: `te`, `test.rs:22`, `te:22:5`, etc.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
|
||||
pub struct PathLikeWithPosition<P> {
|
||||
pub path_like: P,
|
||||
pub struct PathWithPosition {
|
||||
pub path: PathBuf,
|
||||
pub row: Option<u32>,
|
||||
// Absent if row is absent.
|
||||
pub column: Option<u32>,
|
||||
}
|
||||
|
||||
impl<P> PathLikeWithPosition<P> {
|
||||
/// Returns a PathLikeWithPosition from a path.
|
||||
pub fn from_path(path: P) -> Self {
|
||||
impl PathWithPosition {
|
||||
/// Returns a PathWithPosition from a path.
|
||||
pub fn from_path(path: PathBuf) -> Self {
|
||||
Self {
|
||||
path_like: path,
|
||||
path,
|
||||
row: None,
|
||||
column: None,
|
||||
}
|
||||
}
|
||||
/// Parses a string that possibly has `:row:column` suffix.
|
||||
/// 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 on Windows, `s` will replace `/` with `\` for compatibility.
|
||||
pub fn parse_str<E>(
|
||||
s: &str,
|
||||
parse_path_like_str: impl Fn(&str, &str) -> Result<P, E>,
|
||||
) -> Result<Self, E> {
|
||||
#[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,
|
||||
})
|
||||
/// If the suffix parsing fails, the whole string is parsed as a path.
|
||||
pub fn parse_str(s: &str) -> Self {
|
||||
let fallback = |fallback_str| Self {
|
||||
path: Path::new(fallback_str).to_path_buf(),
|
||||
row: None,
|
||||
column: None,
|
||||
};
|
||||
|
||||
let trimmed = s.trim();
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
let is_absolute = trimmed.starts_with(r"\\?\");
|
||||
if is_absolute {
|
||||
return Self::parse_absolute_path(trimmed, |p| parse_path_like_str(s, p));
|
||||
}
|
||||
let path = Path::new(trimmed);
|
||||
let maybe_file_name_with_row_col = path
|
||||
.file_name()
|
||||
.unwrap_or_default()
|
||||
.to_str()
|
||||
.unwrap_or_default();
|
||||
if maybe_file_name_with_row_col.is_empty() {
|
||||
return fallback(s);
|
||||
}
|
||||
|
||||
match trimmed.split_once(FILE_ROW_COLUMN_DELIMITER) {
|
||||
Some((path_like_str, maybe_row_and_col_str)) => {
|
||||
let path_like_str = path_like_str.trim();
|
||||
match maybe_file_name_with_row_col.split_once(FILE_ROW_COLUMN_DELIMITER) {
|
||||
Some((file_name, maybe_row_and_col_str)) => {
|
||||
let file_name = file_name.trim();
|
||||
let maybe_row_and_col_str = maybe_row_and_col_str.trim();
|
||||
if path_like_str.is_empty() {
|
||||
fallback(s)
|
||||
} else if maybe_row_and_col_str.is_empty() {
|
||||
fallback(path_like_str)
|
||||
if file_name.is_empty() {
|
||||
return fallback(s);
|
||||
}
|
||||
|
||||
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 {
|
||||
let (row_parse_result, maybe_col_str) =
|
||||
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>(), ""),
|
||||
};
|
||||
|
||||
let path = Path::new(path_without_suffix).to_path_buf();
|
||||
|
||||
match row_parse_result {
|
||||
Ok(row) => {
|
||||
if maybe_col_str.is_empty() {
|
||||
Ok(Self {
|
||||
path_like: parse_path_like_str(s, path_like_str)?,
|
||||
Self {
|
||||
path,
|
||||
row: Some(row),
|
||||
column: None,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
let (maybe_col_str, _) =
|
||||
maybe_col_str.split_once(':').unwrap_or((maybe_col_str, ""));
|
||||
match maybe_col_str.parse::<u32>() {
|
||||
Ok(col) => Ok(Self {
|
||||
path_like: parse_path_like_str(s, path_like_str)?,
|
||||
Ok(col) => Self {
|
||||
path,
|
||||
row: Some(row),
|
||||
column: Some(col),
|
||||
}),
|
||||
Err(_) => Ok(Self {
|
||||
path_like: parse_path_like_str(s, path_like_str)?,
|
||||
},
|
||||
Err(_) => Self {
|
||||
path,
|
||||
row: Some(row),
|
||||
column: None,
|
||||
}),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => Ok(Self {
|
||||
path_like: parse_path_like_str(s, path_like_str)?,
|
||||
Err(_) => Self {
|
||||
path,
|
||||
row: 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.
|
||||
#[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>(
|
||||
pub fn map_path<E>(
|
||||
self,
|
||||
mapping: impl FnOnce(P) -> Result<P2, E>,
|
||||
) -> Result<PathLikeWithPosition<P2>, E> {
|
||||
Ok(PathLikeWithPosition {
|
||||
path_like: mapping(self.path_like)?,
|
||||
mapping: impl FnOnce(PathBuf) -> Result<PathBuf, E>,
|
||||
) -> Result<PathWithPosition, E> {
|
||||
Ok(PathWithPosition {
|
||||
path: mapping(self.path)?,
|
||||
row: self.row,
|
||||
column: self.column,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn to_string(&self, path_like_to_string: impl Fn(&P) -> String) -> String {
|
||||
let path_like_string = path_like_to_string(&self.path_like);
|
||||
pub fn to_string(&self, path_to_string: impl Fn(&PathBuf) -> String) -> String {
|
||||
let path_string = path_to_string(&self.path);
|
||||
if let Some(row) = self.row {
|
||||
if let Some(column) = self.column {
|
||||
format!("{path_like_string}:{row}:{column}")
|
||||
format!("{path_string}:{row}:{column}")
|
||||
} else {
|
||||
format!("{path_like_string}:{row}")
|
||||
format!("{path_string}:{row}")
|
||||
}
|
||||
} else {
|
||||
path_like_string
|
||||
path_string
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -335,38 +282,29 @@ impl PathMatcher {
|
||||
mod tests {
|
||||
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]
|
||||
fn path_with_position_parsing_positive() {
|
||||
let input_and_expected = [
|
||||
(
|
||||
"test_file.rs",
|
||||
PathLikeWithPosition {
|
||||
path_like: ("test_file.rs".to_string(), "test_file.rs".to_string()),
|
||||
PathWithPosition {
|
||||
path: PathBuf::from("test_file.rs"),
|
||||
row: None,
|
||||
column: None,
|
||||
},
|
||||
),
|
||||
(
|
||||
"test_file.rs:1",
|
||||
PathLikeWithPosition {
|
||||
path_like: ("test_file.rs:1".to_string(), "test_file.rs".to_string()),
|
||||
PathWithPosition {
|
||||
path: PathBuf::from("test_file.rs"),
|
||||
row: Some(1),
|
||||
column: None,
|
||||
},
|
||||
),
|
||||
(
|
||||
"test_file.rs:1:2",
|
||||
PathLikeWithPosition {
|
||||
path_like: ("test_file.rs:1:2".to_string(), "test_file.rs".to_string()),
|
||||
PathWithPosition {
|
||||
path: PathBuf::from("test_file.rs"),
|
||||
row: Some(1),
|
||||
column: Some(2),
|
||||
},
|
||||
@ -374,7 +312,7 @@ mod tests {
|
||||
];
|
||||
|
||||
for (input, expected) in input_and_expected {
|
||||
let actual = parse_str(input);
|
||||
let actual = PathWithPosition::parse_str(input);
|
||||
assert_eq!(
|
||||
actual, expected,
|
||||
"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:3", Some(1), Some(2)),
|
||||
] {
|
||||
let actual = parse_str(input);
|
||||
let actual = PathWithPosition::parse_str(input);
|
||||
assert_eq!(
|
||||
actual,
|
||||
PathLikeWithPosition {
|
||||
path_like: (input.to_string(), "test_file.rs".to_string()),
|
||||
PathWithPosition {
|
||||
path: PathBuf::from("test_file.rs"),
|
||||
row,
|
||||
column,
|
||||
},
|
||||
@ -414,27 +352,24 @@ mod tests {
|
||||
let input_and_expected = [
|
||||
(
|
||||
"test_file.rs:",
|
||||
PathLikeWithPosition {
|
||||
path_like: ("test_file.rs:".to_string(), "test_file.rs".to_string()),
|
||||
PathWithPosition {
|
||||
path: PathBuf::from("test_file.rs"),
|
||||
row: None,
|
||||
column: None,
|
||||
},
|
||||
),
|
||||
(
|
||||
"test_file.rs:1:",
|
||||
PathLikeWithPosition {
|
||||
path_like: ("test_file.rs:1:".to_string(), "test_file.rs".to_string()),
|
||||
PathWithPosition {
|
||||
path: PathBuf::from("test_file.rs"),
|
||||
row: Some(1),
|
||||
column: None,
|
||||
},
|
||||
),
|
||||
(
|
||||
"crates/file_finder/src/file_finder.rs:1902:13:",
|
||||
PathLikeWithPosition {
|
||||
path_like: (
|
||||
"crates/file_finder/src/file_finder.rs:1902:13:".to_string(),
|
||||
"crates/file_finder/src/file_finder.rs".to_string(),
|
||||
),
|
||||
PathWithPosition {
|
||||
path: PathBuf::from("crates/file_finder/src/file_finder.rs"),
|
||||
row: Some(1902),
|
||||
column: Some(13),
|
||||
},
|
||||
@ -445,71 +380,64 @@ mod tests {
|
||||
let input_and_expected = [
|
||||
(
|
||||
"test_file.rs:",
|
||||
PathLikeWithPosition {
|
||||
path_like: ("test_file.rs:".to_string(), "test_file.rs".to_string()),
|
||||
PathWithPosition {
|
||||
path: PathBuf::from("test_file.rs"),
|
||||
row: None,
|
||||
column: None,
|
||||
},
|
||||
),
|
||||
(
|
||||
"test_file.rs:1:",
|
||||
PathLikeWithPosition {
|
||||
path_like: ("test_file.rs:1:".to_string(), "test_file.rs".to_string()),
|
||||
PathWithPosition {
|
||||
path: PathBuf::from("test_file.rs"),
|
||||
row: Some(1),
|
||||
column: None,
|
||||
},
|
||||
),
|
||||
(
|
||||
"\\\\?\\C:\\Users\\someone\\test_file.rs:1902:13:",
|
||||
PathLikeWithPosition {
|
||||
path_like: (
|
||||
"\\\\?\\C:\\Users\\someone\\test_file.rs:1902:13:".to_string(),
|
||||
"C:\\Users\\someone\\test_file.rs".to_string(),
|
||||
),
|
||||
PathWithPosition {
|
||||
path: PathBuf::from("\\\\?\\C:\\Users\\someone\\test_file.rs"),
|
||||
row: Some(1902),
|
||||
column: Some(13),
|
||||
},
|
||||
),
|
||||
(
|
||||
"\\\\?\\C:\\Users\\someone\\test_file.rs:1902:13:15:",
|
||||
PathLikeWithPosition {
|
||||
path_like: (
|
||||
"\\\\?\\C:\\Users\\someone\\test_file.rs:1902:13:15:".to_string(),
|
||||
"C:\\Users\\someone\\test_file.rs".to_string(),
|
||||
),
|
||||
PathWithPosition {
|
||||
path: PathBuf::from("\\\\?\\C:\\Users\\someone\\test_file.rs"),
|
||||
row: Some(1902),
|
||||
column: Some(13),
|
||||
},
|
||||
),
|
||||
(
|
||||
"\\\\?\\C:\\Users\\someone\\test_file.rs:1902:::15:",
|
||||
PathLikeWithPosition {
|
||||
path_like: (
|
||||
"\\\\?\\C:\\Users\\someone\\test_file.rs:1902:::15:".to_string(),
|
||||
"C:\\Users\\someone\\test_file.rs".to_string(),
|
||||
),
|
||||
PathWithPosition {
|
||||
path: PathBuf::from("\\\\?\\C:\\Users\\someone\\test_file.rs"),
|
||||
row: Some(1902),
|
||||
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",
|
||||
PathLikeWithPosition {
|
||||
path_like: (
|
||||
"crates\\utils\\paths.rs".to_string(),
|
||||
"crates\\utils\\paths.rs".to_string(),
|
||||
),
|
||||
PathWithPosition {
|
||||
path: PathBuf::from("crates\\utils\\paths.rs"),
|
||||
row: None,
|
||||
column: None,
|
||||
},
|
||||
),
|
||||
(
|
||||
"crates/utils/paths.rs:101",
|
||||
PathLikeWithPosition {
|
||||
path_like: (
|
||||
"crates\\utils\\paths.rs:101".to_string(),
|
||||
"crates\\utils\\paths.rs".to_string(),
|
||||
),
|
||||
PathWithPosition {
|
||||
path: PathBuf::from("crates\\utils\\paths.rs"),
|
||||
row: Some(101),
|
||||
column: None,
|
||||
},
|
||||
@ -517,7 +445,7 @@ mod tests {
|
||||
];
|
||||
|
||||
for (input, expected) in input_and_expected {
|
||||
let actual = parse_str(input);
|
||||
let actual = PathWithPosition::parse_str(input);
|
||||
assert_eq!(
|
||||
actual, expected,
|
||||
"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 language::{Bias, Point};
|
||||
use remote::SshConnectionOptions;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use std::{process, thread};
|
||||
use util::paths::PathLikeWithPosition;
|
||||
use util::paths::PathWithPosition;
|
||||
use util::ResultExt;
|
||||
use welcome::{show_welcome_view, FIRST_OPEN};
|
||||
use workspace::item::ItemHandle;
|
||||
@ -28,7 +26,7 @@ use workspace::{AppState, Workspace};
|
||||
#[derive(Default, Debug)]
|
||||
pub struct OpenRequest {
|
||||
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 join_channel: Option<u64>,
|
||||
pub ssh_connection: Option<SshConnectionOptions>,
|
||||
@ -58,11 +56,8 @@ impl OpenRequest {
|
||||
|
||||
fn parse_file_path(&mut self, file: &str) {
|
||||
if let Some(decoded) = urlencoding::decode(file).log_err() {
|
||||
if let Some(path_buf) =
|
||||
PathLikeWithPosition::parse_str(&decoded, |_, s| PathBuf::try_from(s)).log_err()
|
||||
{
|
||||
self.open_paths.push(path_buf)
|
||||
}
|
||||
let path_buf = PathWithPosition::parse_str(&decoded);
|
||||
self.open_paths.push(path_buf)
|
||||
}
|
||||
}
|
||||
|
||||
@ -193,7 +188,7 @@ fn connect_to_cli(
|
||||
}
|
||||
|
||||
pub async fn open_paths_with_positions(
|
||||
path_likes: &Vec<PathLikeWithPosition<PathBuf>>,
|
||||
path_positions: &Vec<PathWithPosition>,
|
||||
app_state: Arc<AppState>,
|
||||
open_options: workspace::OpenOptions,
|
||||
cx: &mut AsyncAppContext,
|
||||
@ -203,10 +198,10 @@ pub async fn open_paths_with_positions(
|
||||
)> {
|
||||
let mut caret_positions = HashMap::default();
|
||||
|
||||
let paths = path_likes
|
||||
let paths = path_positions
|
||||
.iter()
|
||||
.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 path.is_file() {
|
||||
let row = row.saturating_sub(1);
|
||||
@ -364,8 +359,8 @@ async fn open_workspaces(
|
||||
location
|
||||
.paths()
|
||||
.iter()
|
||||
.map(|path| PathLikeWithPosition {
|
||||
path_like: path.clone(),
|
||||
.map(|path| PathWithPosition {
|
||||
path: path.clone(),
|
||||
row: None,
|
||||
column: None,
|
||||
})
|
||||
@ -380,10 +375,7 @@ async fn open_workspaces(
|
||||
let paths_with_position = paths
|
||||
.into_iter()
|
||||
.map(|path_with_position_string| {
|
||||
PathLikeWithPosition::parse_str(&path_with_position_string, |_, path_str| {
|
||||
Ok::<_, std::convert::Infallible>(Path::new(path_str).to_path_buf())
|
||||
})
|
||||
.expect("Infallible")
|
||||
PathWithPosition::parse_str(&path_with_position_string)
|
||||
})
|
||||
.collect();
|
||||
vec![paths_with_position]
|
||||
@ -434,7 +426,7 @@ async fn open_workspaces(
|
||||
}
|
||||
|
||||
async fn open_workspace(
|
||||
workspace_paths: Vec<PathLikeWithPosition<PathBuf>>,
|
||||
workspace_paths: Vec<PathWithPosition>,
|
||||
open_new_workspace: Option<bool>,
|
||||
wait: bool,
|
||||
responses: &IpcSender<CliResponse>,
|
||||
@ -542,7 +534,7 @@ mod tests {
|
||||
use editor::Editor;
|
||||
use gpui::TestAppContext;
|
||||
use serde_json::json;
|
||||
use util::paths::PathLikeWithPosition;
|
||||
use util::paths::PathWithPosition;
|
||||
use workspace::{AppState, Workspace};
|
||||
|
||||
use crate::zed::{open_listener::open_workspace, tests::init_test};
|
||||
@ -656,9 +648,9 @@ mod tests {
|
||||
) {
|
||||
let (response_tx, _) = ipc::channel::<CliResponse>().unwrap();
|
||||
|
||||
let path_like = PathBuf::from(path);
|
||||
let workspace_paths = vec![PathLikeWithPosition {
|
||||
path_like,
|
||||
let path = PathBuf::from(path);
|
||||
let workspace_paths = vec![PathWithPosition {
|
||||
path,
|
||||
row: None,
|
||||
column: None,
|
||||
}];
|
||||
|
Loading…
Reference in New Issue
Block a user