mirror of
https://github.com/zed-industries/zed.git
synced 2026-06-01 03:14:56 +07:00
Represent relative paths using a dedicated, separator-agnostic type (#38744)
Closes https://github.com/zed-industries/zed/issues/38690 Closes #37353 ### Background On Windows, paths are normally separated by `\`, unlike mac and linux where they are separated by `/`. When editing code in a project that uses a different path style than your local system (e.g. remoting from Windows to Linux, using WSL, and collaboration between windows and unix users), the correct separator for a path may differ from the "native" separator. Previously, to work around this, Zed converted paths' separators in numerous places. This was applied to both absolute and relative paths, leading to incorrect conversions in some cases. ### Solution Many code paths in Zed use paths that are *relative* to either a worktree root or a git repository. This PR introduces a dedicated type for these paths called `RelPath`, which stores the path in the same way regardless of host platform, and offers `Path`-like manipulation APIs. RelPath supports *displaying* the path using either separator, so that we can display paths in a style that is determined at runtime based on the current project. The representation of absolute paths is left untouched, for now. Absolute paths are different from relative paths because (except in contexts where we know that the path refers to the local filesystem) they should generally be treated as opaque strings. Currently we use a mix of types for these paths (std::path::Path, String, SanitizedPath). Release Notes: - N/A --------- Co-authored-by: Cole Miller <cole@zed.dev> Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Co-authored-by: Peter Tripp <petertripp@gmail.com> Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com> Co-authored-by: Lukas Wirth <me@lukaswirth.dev>
This commit is contained in:
parent
3c626f3758
commit
03f9cf4414
216 changed files with 5873 additions and 5257 deletions
3
Cargo.lock
generated
3
Cargo.lock
generated
|
|
@ -892,6 +892,7 @@ dependencies = [
|
|||
"serde",
|
||||
"serde_json",
|
||||
"ui",
|
||||
"util",
|
||||
"workspace",
|
||||
"workspace-hack",
|
||||
]
|
||||
|
|
@ -12094,7 +12095,6 @@ dependencies = [
|
|||
"markdown",
|
||||
"node_runtime",
|
||||
"parking_lot",
|
||||
"pathdiff",
|
||||
"paths",
|
||||
"postage",
|
||||
"prettier",
|
||||
|
|
@ -12147,7 +12147,6 @@ dependencies = [
|
|||
"git",
|
||||
"git_ui",
|
||||
"gpui",
|
||||
"indexmap 2.9.0",
|
||||
"language",
|
||||
"menu",
|
||||
"pretty_assertions",
|
||||
|
|
|
|||
|
|
@ -573,7 +573,7 @@ impl ToolCallContent {
|
|||
))),
|
||||
acp::ToolCallContent::Diff { diff } => Ok(Self::Diff(cx.new(|cx| {
|
||||
Diff::finalized(
|
||||
diff.path,
|
||||
diff.path.to_string_lossy().to_string(),
|
||||
diff.old_text,
|
||||
diff.new_text,
|
||||
language_registry,
|
||||
|
|
|
|||
|
|
@ -6,12 +6,7 @@ use itertools::Itertools;
|
|||
use language::{
|
||||
Anchor, Buffer, Capability, LanguageRegistry, OffsetRangeExt as _, Point, Rope, TextBuffer,
|
||||
};
|
||||
use std::{
|
||||
cmp::Reverse,
|
||||
ops::Range,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
use std::{cmp::Reverse, ops::Range, path::Path, sync::Arc};
|
||||
use util::ResultExt;
|
||||
|
||||
pub enum Diff {
|
||||
|
|
@ -21,7 +16,7 @@ pub enum Diff {
|
|||
|
||||
impl Diff {
|
||||
pub fn finalized(
|
||||
path: PathBuf,
|
||||
path: String,
|
||||
old_text: Option<String>,
|
||||
new_text: String,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
|
|
@ -36,7 +31,7 @@ impl Diff {
|
|||
let buffer = new_buffer.clone();
|
||||
async move |_, cx| {
|
||||
let language = language_registry
|
||||
.language_for_file_path(&path)
|
||||
.language_for_file_path(Path::new(&path))
|
||||
.await
|
||||
.log_err();
|
||||
|
||||
|
|
@ -152,12 +147,15 @@ impl Diff {
|
|||
let path = match self {
|
||||
Diff::Pending(PendingDiff {
|
||||
new_buffer: buffer, ..
|
||||
}) => buffer.read(cx).file().map(|file| file.path().as_ref()),
|
||||
Diff::Finalized(FinalizedDiff { path, .. }) => Some(path.as_path()),
|
||||
}) => buffer
|
||||
.read(cx)
|
||||
.file()
|
||||
.map(|file| file.path().display(file.path_style(cx))),
|
||||
Diff::Finalized(FinalizedDiff { path, .. }) => Some(path.as_str().into()),
|
||||
};
|
||||
format!(
|
||||
"Diff: {}\n```\n{}\n```\n",
|
||||
path.unwrap_or(Path::new("untitled")).display(),
|
||||
path.unwrap_or("untitled".into()),
|
||||
buffer_text
|
||||
)
|
||||
}
|
||||
|
|
@ -244,8 +242,8 @@ impl PendingDiff {
|
|||
.new_buffer
|
||||
.read(cx)
|
||||
.file()
|
||||
.map(|file| file.path().as_ref())
|
||||
.unwrap_or(Path::new("untitled"))
|
||||
.map(|file| file.path().display(file.path_style(cx)))
|
||||
.unwrap_or("untitled".into())
|
||||
.into();
|
||||
|
||||
// Replace the buffer in the multibuffer with the snapshot
|
||||
|
|
@ -348,7 +346,7 @@ impl PendingDiff {
|
|||
}
|
||||
|
||||
pub struct FinalizedDiff {
|
||||
path: PathBuf,
|
||||
path: String,
|
||||
base_text: Arc<String>,
|
||||
new_buffer: Entity<Buffer>,
|
||||
multibuffer: Entity<MultiBuffer>,
|
||||
|
|
|
|||
|
|
@ -8,10 +8,7 @@ use language::{Anchor, Buffer, BufferEvent, DiskState, Point, ToPoint};
|
|||
use project::{Project, ProjectItem, lsp_store::OpenLspBufferHandle};
|
||||
use std::{cmp, ops::Range, sync::Arc};
|
||||
use text::{Edit, Patch, Rope};
|
||||
use util::{
|
||||
RangeExt, ResultExt as _,
|
||||
paths::{PathStyle, RemotePathBuf},
|
||||
};
|
||||
use util::{RangeExt, ResultExt as _};
|
||||
|
||||
/// Tracks actions performed by tools in a thread
|
||||
pub struct ActionLog {
|
||||
|
|
@ -62,7 +59,13 @@ impl ActionLog {
|
|||
let file_path = buffer
|
||||
.read(cx)
|
||||
.file()
|
||||
.map(|file| RemotePathBuf::new(file.full_path(cx), PathStyle::Posix).to_proto())
|
||||
.map(|file| {
|
||||
let mut path = file.full_path(cx).to_string_lossy().into_owned();
|
||||
if file.path_style(cx).is_windows() {
|
||||
path = path.replace('\\', "/");
|
||||
}
|
||||
path
|
||||
})
|
||||
.unwrap_or_else(|| format!("buffer_{}", buffer.entity_id()));
|
||||
|
||||
let mut result = String::new();
|
||||
|
|
@ -2301,7 +2304,7 @@ mod tests {
|
|||
.await;
|
||||
fs.set_head_for_repo(
|
||||
path!("/project/.git").as_ref(),
|
||||
&[("file.txt".into(), "a\nb\nc\nd\ne\nf\ng\nh\ni\nj".into())],
|
||||
&[("file.txt", "a\nb\nc\nd\ne\nf\ng\nh\ni\nj".into())],
|
||||
"0000000",
|
||||
);
|
||||
cx.run_until_parked();
|
||||
|
|
@ -2384,7 +2387,7 @@ mod tests {
|
|||
// - Ignores the last line edit (j stays as j)
|
||||
fs.set_head_for_repo(
|
||||
path!("/project/.git").as_ref(),
|
||||
&[("file.txt".into(), "A\nb\nc\nf\nG\nh\ni\nj".into())],
|
||||
&[("file.txt", "A\nb\nc\nf\nG\nh\ni\nj".into())],
|
||||
"0000001",
|
||||
);
|
||||
cx.run_until_parked();
|
||||
|
|
@ -2415,10 +2418,7 @@ mod tests {
|
|||
// Make another commit that accepts the NEW line but with different content
|
||||
fs.set_head_for_repo(
|
||||
path!("/project/.git").as_ref(),
|
||||
&[(
|
||||
"file.txt".into(),
|
||||
"A\nb\nc\nf\nGGG\nh\nDIFFERENT\ni\nj".into(),
|
||||
)],
|
||||
&[("file.txt", "A\nb\nc\nf\nGGG\nh\nDIFFERENT\ni\nj".into())],
|
||||
"0000002",
|
||||
);
|
||||
cx.run_until_parked();
|
||||
|
|
@ -2444,7 +2444,7 @@ mod tests {
|
|||
// Final commit that accepts all remaining edits
|
||||
fs.set_head_for_repo(
|
||||
path!("/project/.git").as_ref(),
|
||||
&[("file.txt".into(), "A\nb\nc\nf\nGGG\nh\nNEW\ni\nJ".into())],
|
||||
&[("file.txt", "A\nb\nc\nf\nGGG\nh\nNEW\ni\nJ".into())],
|
||||
"0000003",
|
||||
);
|
||||
cx.run_until_parked();
|
||||
|
|
|
|||
|
|
@ -9,12 +9,14 @@ pub mod tool_use;
|
|||
|
||||
pub use context::{AgentContext, ContextId, ContextLoadResult};
|
||||
pub use context_store::ContextStore;
|
||||
use fs::Fs;
|
||||
use std::sync::Arc;
|
||||
pub use thread::{
|
||||
LastRestoreCheckpoint, Message, MessageCrease, MessageId, MessageSegment, Thread, ThreadError,
|
||||
ThreadEvent, ThreadFeedback, ThreadId, ThreadSummary, TokenUsageRatio,
|
||||
};
|
||||
pub use thread_store::{SerializedThread, TextThreadStore, ThreadStore};
|
||||
|
||||
pub fn init(cx: &mut gpui::App) {
|
||||
thread_store::init(cx);
|
||||
pub fn init(fs: Arc<dyn Fs>, cx: &mut gpui::App) {
|
||||
thread_store::init(fs, cx);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ use std::path::PathBuf;
|
|||
use std::{ops::Range, path::Path, sync::Arc};
|
||||
use text::{Anchor, OffsetRangeExt as _};
|
||||
use util::markdown::MarkdownCodeBlock;
|
||||
use util::rel_path::RelPath;
|
||||
use util::{ResultExt as _, post_inc};
|
||||
|
||||
pub const RULES_ICON: IconName = IconName::Reader;
|
||||
|
|
@ -242,7 +243,7 @@ pub struct DirectoryContext {
|
|||
#[derive(Debug, Clone)]
|
||||
pub struct DirectoryContextDescendant {
|
||||
/// Path within the directory.
|
||||
pub rel_path: Arc<Path>,
|
||||
pub rel_path: Arc<RelPath>,
|
||||
pub fenced_codeblock: SharedString,
|
||||
}
|
||||
|
||||
|
|
@ -968,7 +969,7 @@ pub fn load_context(
|
|||
})
|
||||
}
|
||||
|
||||
fn collect_files_in_path(worktree: &Worktree, path: &Path) -> Vec<Arc<Path>> {
|
||||
fn collect_files_in_path(worktree: &Worktree, path: &RelPath) -> Vec<Arc<RelPath>> {
|
||||
let mut files = Vec::new();
|
||||
|
||||
for entry in worktree.child_entries(path) {
|
||||
|
|
|
|||
|
|
@ -14,7 +14,10 @@ use futures::{self, FutureExt};
|
|||
use gpui::{App, Context, Entity, EventEmitter, Image, SharedString, Task, WeakEntity};
|
||||
use language::{Buffer, File as _};
|
||||
use language_model::LanguageModelImage;
|
||||
use project::{Project, ProjectItem, ProjectPath, Symbol, image_store::is_image_file};
|
||||
use project::{
|
||||
Project, ProjectItem, ProjectPath, Symbol, image_store::is_image_file,
|
||||
lsp_store::SymbolLocation,
|
||||
};
|
||||
use prompt_store::UserPromptId;
|
||||
use ref_cast::RefCast as _;
|
||||
use std::{
|
||||
|
|
@ -500,7 +503,7 @@ impl ContextStore {
|
|||
let Some(context_path) = buffer.project_path(cx) else {
|
||||
return false;
|
||||
};
|
||||
if context_path != symbol.path {
|
||||
if symbol.path != SymbolLocation::InProject(context_path) {
|
||||
return false;
|
||||
}
|
||||
let context_range = context.range.to_point_utf16(&buffer.snapshot());
|
||||
|
|
|
|||
|
|
@ -234,7 +234,6 @@ impl MessageSegment {
|
|||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct ProjectSnapshot {
|
||||
pub worktree_snapshots: Vec<WorktreeSnapshot>,
|
||||
pub unsaved_buffer_paths: Vec<String>,
|
||||
pub timestamp: DateTime<Utc>,
|
||||
}
|
||||
|
||||
|
|
@ -2857,27 +2856,11 @@ impl Thread {
|
|||
.map(|worktree| Self::worktree_snapshot(worktree, git_store.clone(), cx))
|
||||
.collect();
|
||||
|
||||
cx.spawn(async move |_, cx| {
|
||||
cx.spawn(async move |_, _| {
|
||||
let worktree_snapshots = futures::future::join_all(worktree_snapshots).await;
|
||||
|
||||
let mut unsaved_buffers = Vec::new();
|
||||
cx.update(|app_cx| {
|
||||
let buffer_store = project.read(app_cx).buffer_store();
|
||||
for buffer_handle in buffer_store.read(app_cx).buffers() {
|
||||
let buffer = buffer_handle.read(app_cx);
|
||||
if buffer.is_dirty()
|
||||
&& let Some(file) = buffer.file()
|
||||
{
|
||||
let path = file.path().to_string_lossy().to_string();
|
||||
unsaved_buffers.push(path);
|
||||
}
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
|
||||
Arc::new(ProjectSnapshot {
|
||||
worktree_snapshots,
|
||||
unsaved_buffer_paths: unsaved_buffers,
|
||||
timestamp: Utc::now(),
|
||||
})
|
||||
})
|
||||
|
|
@ -3275,6 +3258,7 @@ mod tests {
|
|||
use agent_settings::{AgentProfileId, AgentSettings};
|
||||
use assistant_tool::ToolRegistry;
|
||||
use assistant_tools;
|
||||
use fs::Fs;
|
||||
use futures::StreamExt;
|
||||
use futures::future::BoxFuture;
|
||||
use futures::stream::BoxStream;
|
||||
|
|
@ -3298,9 +3282,10 @@ mod tests {
|
|||
|
||||
#[gpui::test]
|
||||
async fn test_message_with_context(cx: &mut TestAppContext) {
|
||||
init_test_settings(cx);
|
||||
let fs = init_test_settings(cx);
|
||||
|
||||
let project = create_test_project(
|
||||
&fs,
|
||||
cx,
|
||||
json!({"code.rs": "fn main() {\n println!(\"Hello, world!\");\n}"}),
|
||||
)
|
||||
|
|
@ -3375,9 +3360,10 @@ fn main() {{
|
|||
|
||||
#[gpui::test]
|
||||
async fn test_only_include_new_contexts(cx: &mut TestAppContext) {
|
||||
init_test_settings(cx);
|
||||
let fs = init_test_settings(cx);
|
||||
|
||||
let project = create_test_project(
|
||||
&fs,
|
||||
cx,
|
||||
json!({
|
||||
"file1.rs": "fn function1() {}\n",
|
||||
|
|
@ -3531,9 +3517,10 @@ fn main() {{
|
|||
|
||||
#[gpui::test]
|
||||
async fn test_message_without_files(cx: &mut TestAppContext) {
|
||||
init_test_settings(cx);
|
||||
let fs = init_test_settings(cx);
|
||||
|
||||
let project = create_test_project(
|
||||
&fs,
|
||||
cx,
|
||||
json!({"code.rs": "fn main() {\n println!(\"Hello, world!\");\n}"}),
|
||||
)
|
||||
|
|
@ -3610,9 +3597,10 @@ fn main() {{
|
|||
#[gpui::test]
|
||||
#[ignore] // turn this test on when project_notifications tool is re-enabled
|
||||
async fn test_stale_buffer_notification(cx: &mut TestAppContext) {
|
||||
init_test_settings(cx);
|
||||
let fs = init_test_settings(cx);
|
||||
|
||||
let project = create_test_project(
|
||||
&fs,
|
||||
cx,
|
||||
json!({"code.rs": "fn main() {\n println!(\"Hello, world!\");\n}"}),
|
||||
)
|
||||
|
|
@ -3738,9 +3726,10 @@ fn main() {{
|
|||
|
||||
#[gpui::test]
|
||||
async fn test_storing_profile_setting_per_thread(cx: &mut TestAppContext) {
|
||||
init_test_settings(cx);
|
||||
let fs = init_test_settings(cx);
|
||||
|
||||
let project = create_test_project(
|
||||
&fs,
|
||||
cx,
|
||||
json!({"code.rs": "fn main() {\n println!(\"Hello, world!\");\n}"}),
|
||||
)
|
||||
|
|
@ -3760,9 +3749,10 @@ fn main() {{
|
|||
|
||||
#[gpui::test]
|
||||
async fn test_serializing_thread_profile(cx: &mut TestAppContext) {
|
||||
init_test_settings(cx);
|
||||
let fs = init_test_settings(cx);
|
||||
|
||||
let project = create_test_project(
|
||||
&fs,
|
||||
cx,
|
||||
json!({"code.rs": "fn main() {\n println!(\"Hello, world!\");\n}"}),
|
||||
)
|
||||
|
|
@ -3803,9 +3793,10 @@ fn main() {{
|
|||
|
||||
#[gpui::test]
|
||||
async fn test_temperature_setting(cx: &mut TestAppContext) {
|
||||
init_test_settings(cx);
|
||||
let fs = init_test_settings(cx);
|
||||
|
||||
let project = create_test_project(
|
||||
&fs,
|
||||
cx,
|
||||
json!({"code.rs": "fn main() {\n println!(\"Hello, world!\");\n}"}),
|
||||
)
|
||||
|
|
@ -3897,9 +3888,9 @@ fn main() {{
|
|||
|
||||
#[gpui::test]
|
||||
async fn test_thread_summary(cx: &mut TestAppContext) {
|
||||
init_test_settings(cx);
|
||||
let fs = init_test_settings(cx);
|
||||
|
||||
let project = create_test_project(cx, json!({})).await;
|
||||
let project = create_test_project(&fs, cx, json!({})).await;
|
||||
|
||||
let (_, _thread_store, thread, _context_store, model) =
|
||||
setup_test_environment(cx, project.clone()).await;
|
||||
|
|
@ -3982,9 +3973,9 @@ fn main() {{
|
|||
|
||||
#[gpui::test]
|
||||
async fn test_thread_summary_error_set_manually(cx: &mut TestAppContext) {
|
||||
init_test_settings(cx);
|
||||
let fs = init_test_settings(cx);
|
||||
|
||||
let project = create_test_project(cx, json!({})).await;
|
||||
let project = create_test_project(&fs, cx, json!({})).await;
|
||||
|
||||
let (_, _thread_store, thread, _context_store, model) =
|
||||
setup_test_environment(cx, project.clone()).await;
|
||||
|
|
@ -4004,9 +3995,9 @@ fn main() {{
|
|||
|
||||
#[gpui::test]
|
||||
async fn test_thread_summary_error_retry(cx: &mut TestAppContext) {
|
||||
init_test_settings(cx);
|
||||
let fs = init_test_settings(cx);
|
||||
|
||||
let project = create_test_project(cx, json!({})).await;
|
||||
let project = create_test_project(&fs, cx, json!({})).await;
|
||||
|
||||
let (_, _thread_store, thread, _context_store, model) =
|
||||
setup_test_environment(cx, project.clone()).await;
|
||||
|
|
@ -4158,9 +4149,9 @@ fn main() {{
|
|||
|
||||
#[gpui::test]
|
||||
async fn test_retry_on_overloaded_error(cx: &mut TestAppContext) {
|
||||
init_test_settings(cx);
|
||||
let fs = init_test_settings(cx);
|
||||
|
||||
let project = create_test_project(cx, json!({})).await;
|
||||
let project = create_test_project(&fs, cx, json!({})).await;
|
||||
let (_, _, thread, _, _base_model) = setup_test_environment(cx, project.clone()).await;
|
||||
|
||||
// Enable Burn Mode to allow retries
|
||||
|
|
@ -4236,9 +4227,9 @@ fn main() {{
|
|||
|
||||
#[gpui::test]
|
||||
async fn test_retry_on_internal_server_error(cx: &mut TestAppContext) {
|
||||
init_test_settings(cx);
|
||||
let fs = init_test_settings(cx);
|
||||
|
||||
let project = create_test_project(cx, json!({})).await;
|
||||
let project = create_test_project(&fs, cx, json!({})).await;
|
||||
let (_, _, thread, _, _base_model) = setup_test_environment(cx, project.clone()).await;
|
||||
|
||||
// Enable Burn Mode to allow retries
|
||||
|
|
@ -4318,9 +4309,9 @@ fn main() {{
|
|||
|
||||
#[gpui::test]
|
||||
async fn test_exponential_backoff_on_retries(cx: &mut TestAppContext) {
|
||||
init_test_settings(cx);
|
||||
let fs = init_test_settings(cx);
|
||||
|
||||
let project = create_test_project(cx, json!({})).await;
|
||||
let project = create_test_project(&fs, cx, json!({})).await;
|
||||
let (_, _, thread, _, _base_model) = setup_test_environment(cx, project.clone()).await;
|
||||
|
||||
// Enable Burn Mode to allow retries
|
||||
|
|
@ -4438,9 +4429,9 @@ fn main() {{
|
|||
|
||||
#[gpui::test]
|
||||
async fn test_max_retries_exceeded(cx: &mut TestAppContext) {
|
||||
init_test_settings(cx);
|
||||
let fs = init_test_settings(cx);
|
||||
|
||||
let project = create_test_project(cx, json!({})).await;
|
||||
let project = create_test_project(&fs, cx, json!({})).await;
|
||||
let (_, _, thread, _, _base_model) = setup_test_environment(cx, project.clone()).await;
|
||||
|
||||
// Enable Burn Mode to allow retries
|
||||
|
|
@ -4529,9 +4520,9 @@ fn main() {{
|
|||
|
||||
#[gpui::test]
|
||||
async fn test_retry_message_removed_on_retry(cx: &mut TestAppContext) {
|
||||
init_test_settings(cx);
|
||||
let fs = init_test_settings(cx);
|
||||
|
||||
let project = create_test_project(cx, json!({})).await;
|
||||
let project = create_test_project(&fs, cx, json!({})).await;
|
||||
let (_, _, thread, _, _base_model) = setup_test_environment(cx, project.clone()).await;
|
||||
|
||||
// Enable Burn Mode to allow retries
|
||||
|
|
@ -4702,9 +4693,9 @@ fn main() {{
|
|||
|
||||
#[gpui::test]
|
||||
async fn test_successful_completion_clears_retry_state(cx: &mut TestAppContext) {
|
||||
init_test_settings(cx);
|
||||
let fs = init_test_settings(cx);
|
||||
|
||||
let project = create_test_project(cx, json!({})).await;
|
||||
let project = create_test_project(&fs, cx, json!({})).await;
|
||||
let (_, _, thread, _, _base_model) = setup_test_environment(cx, project.clone()).await;
|
||||
|
||||
// Enable Burn Mode to allow retries
|
||||
|
|
@ -4868,9 +4859,9 @@ fn main() {{
|
|||
|
||||
#[gpui::test]
|
||||
async fn test_rate_limit_retry_single_attempt(cx: &mut TestAppContext) {
|
||||
init_test_settings(cx);
|
||||
let fs = init_test_settings(cx);
|
||||
|
||||
let project = create_test_project(cx, json!({})).await;
|
||||
let project = create_test_project(&fs, cx, json!({})).await;
|
||||
let (_, _, thread, _, _base_model) = setup_test_environment(cx, project.clone()).await;
|
||||
|
||||
// Enable Burn Mode to allow retries
|
||||
|
|
@ -5053,9 +5044,9 @@ fn main() {{
|
|||
|
||||
#[gpui::test]
|
||||
async fn test_ui_only_messages_not_sent_to_model(cx: &mut TestAppContext) {
|
||||
init_test_settings(cx);
|
||||
let fs = init_test_settings(cx);
|
||||
|
||||
let project = create_test_project(cx, json!({})).await;
|
||||
let project = create_test_project(&fs, cx, json!({})).await;
|
||||
let (_, _, thread, _, model) = setup_test_environment(cx, project.clone()).await;
|
||||
|
||||
// Insert a regular user message
|
||||
|
|
@ -5153,9 +5144,9 @@ fn main() {{
|
|||
|
||||
#[gpui::test]
|
||||
async fn test_no_retry_without_burn_mode(cx: &mut TestAppContext) {
|
||||
init_test_settings(cx);
|
||||
let fs = init_test_settings(cx);
|
||||
|
||||
let project = create_test_project(cx, json!({})).await;
|
||||
let project = create_test_project(&fs, cx, json!({})).await;
|
||||
let (_, _, thread, _, _base_model) = setup_test_environment(cx, project.clone()).await;
|
||||
|
||||
// Ensure we're in Normal mode (not Burn mode)
|
||||
|
|
@ -5226,9 +5217,9 @@ fn main() {{
|
|||
|
||||
#[gpui::test]
|
||||
async fn test_retry_canceled_on_stop(cx: &mut TestAppContext) {
|
||||
init_test_settings(cx);
|
||||
let fs = init_test_settings(cx);
|
||||
|
||||
let project = create_test_project(cx, json!({})).await;
|
||||
let project = create_test_project(&fs, cx, json!({})).await;
|
||||
let (_, _, thread, _, _base_model) = setup_test_environment(cx, project.clone()).await;
|
||||
|
||||
// Enable Burn Mode to allow retries
|
||||
|
|
@ -5334,7 +5325,8 @@ fn main() {{
|
|||
cx.run_until_parked();
|
||||
}
|
||||
|
||||
fn init_test_settings(cx: &mut TestAppContext) {
|
||||
fn init_test_settings(cx: &mut TestAppContext) -> Arc<dyn Fs> {
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
cx.update(|cx| {
|
||||
let settings_store = SettingsStore::test(cx);
|
||||
cx.set_global(settings_store);
|
||||
|
|
@ -5342,7 +5334,7 @@ fn main() {{
|
|||
Project::init_settings(cx);
|
||||
AgentSettings::register(cx);
|
||||
prompt_store::init(cx);
|
||||
thread_store::init(cx);
|
||||
thread_store::init(fs.clone(), cx);
|
||||
workspace::init_settings(cx);
|
||||
language_model::init_settings(cx);
|
||||
ThemeSettings::register(cx);
|
||||
|
|
@ -5356,16 +5348,17 @@ fn main() {{
|
|||
));
|
||||
assistant_tools::init(http_client, cx);
|
||||
});
|
||||
fs
|
||||
}
|
||||
|
||||
// Helper to create a test project with test files
|
||||
async fn create_test_project(
|
||||
fs: &Arc<dyn Fs>,
|
||||
cx: &mut TestAppContext,
|
||||
files: serde_json::Value,
|
||||
) -> Entity<Project> {
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(path!("/test"), files).await;
|
||||
Project::test(fs, [path!("/test").as_ref()], cx).await
|
||||
fs.as_fake().insert_tree(path!("/test"), files).await;
|
||||
Project::test(fs.clone(), [path!("/test").as_ref()], cx).await
|
||||
}
|
||||
|
||||
async fn setup_test_environment(
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ use assistant_tool::{Tool, ToolId, ToolWorkingSet};
|
|||
use chrono::{DateTime, Utc};
|
||||
use collections::HashMap;
|
||||
use context_server::ContextServerId;
|
||||
use fs::{Fs, RemoveOptions};
|
||||
use futures::{
|
||||
FutureExt as _, StreamExt as _,
|
||||
channel::{mpsc, oneshot},
|
||||
|
|
@ -39,7 +40,7 @@ use std::{
|
|||
rc::Rc,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
use util::ResultExt as _;
|
||||
use util::{ResultExt as _, rel_path::RelPath};
|
||||
|
||||
use zed_env_vars::ZED_STATELESS;
|
||||
|
||||
|
|
@ -85,8 +86,8 @@ const RULES_FILE_NAMES: [&str; 9] = [
|
|||
"GEMINI.md",
|
||||
];
|
||||
|
||||
pub fn init(cx: &mut App) {
|
||||
ThreadsDatabase::init(cx);
|
||||
pub fn init(fs: Arc<dyn Fs>, cx: &mut App) {
|
||||
ThreadsDatabase::init(fs, cx);
|
||||
}
|
||||
|
||||
/// A system prompt shared by all threads created by this ThreadStore
|
||||
|
|
@ -234,7 +235,7 @@ impl ThreadStore {
|
|||
if items.iter().any(|(path, _, _)| {
|
||||
RULES_FILE_NAMES
|
||||
.iter()
|
||||
.any(|name| path.as_ref() == Path::new(name))
|
||||
.any(|name| path.as_ref() == RelPath::new(name).unwrap())
|
||||
}) {
|
||||
self.enqueue_system_prompt_reload();
|
||||
}
|
||||
|
|
@ -327,7 +328,7 @@ impl ThreadStore {
|
|||
cx: &mut App,
|
||||
) -> Task<(WorktreeContext, Option<RulesLoadingError>)> {
|
||||
let tree = worktree.read(cx);
|
||||
let root_name = tree.root_name().into();
|
||||
let root_name = tree.root_name_str().into();
|
||||
let abs_path = tree.abs_path();
|
||||
|
||||
let mut context = WorktreeContext {
|
||||
|
|
@ -367,7 +368,7 @@ impl ThreadStore {
|
|||
.into_iter()
|
||||
.filter_map(|name| {
|
||||
worktree
|
||||
.entry_for_path(name)
|
||||
.entry_for_path(RelPath::new(name).unwrap())
|
||||
.filter(|entry| entry.is_file())
|
||||
.map(|entry| entry.path.clone())
|
||||
})
|
||||
|
|
@ -869,13 +870,13 @@ impl ThreadsDatabase {
|
|||
GlobalThreadsDatabase::global(cx).0.clone()
|
||||
}
|
||||
|
||||
fn init(cx: &mut App) {
|
||||
fn init(fs: Arc<dyn Fs>, cx: &mut App) {
|
||||
let executor = cx.background_executor().clone();
|
||||
let database_future = executor
|
||||
.spawn({
|
||||
let executor = executor.clone();
|
||||
let threads_dir = paths::data_dir().join("threads");
|
||||
async move { ThreadsDatabase::new(threads_dir, executor) }
|
||||
async move { ThreadsDatabase::new(fs, threads_dir, executor).await }
|
||||
})
|
||||
.then(|result| future::ready(result.map(Arc::new).map_err(Arc::new)))
|
||||
.boxed()
|
||||
|
|
@ -884,13 +885,17 @@ impl ThreadsDatabase {
|
|||
cx.set_global(GlobalThreadsDatabase(database_future));
|
||||
}
|
||||
|
||||
pub fn new(threads_dir: PathBuf, executor: BackgroundExecutor) -> Result<Self> {
|
||||
std::fs::create_dir_all(&threads_dir)?;
|
||||
pub async fn new(
|
||||
fs: Arc<dyn Fs>,
|
||||
threads_dir: PathBuf,
|
||||
executor: BackgroundExecutor,
|
||||
) -> Result<Self> {
|
||||
fs.create_dir(&threads_dir).await?;
|
||||
|
||||
let sqlite_path = threads_dir.join("threads.db");
|
||||
let mdb_path = threads_dir.join("threads-db.1.mdb");
|
||||
|
||||
let needs_migration_from_heed = mdb_path.exists();
|
||||
let needs_migration_from_heed = fs.is_file(&mdb_path).await;
|
||||
|
||||
let connection = if *ZED_STATELESS {
|
||||
Connection::open_memory(Some("THREAD_FALLBACK_DB"))
|
||||
|
|
@ -932,7 +937,14 @@ impl ThreadsDatabase {
|
|||
.spawn(async move {
|
||||
log::info!("Starting threads.db migration");
|
||||
Self::migrate_from_heed(&mdb_path, db_connection, executor_clone)?;
|
||||
std::fs::remove_dir_all(mdb_path)?;
|
||||
fs.remove_dir(
|
||||
&mdb_path,
|
||||
RemoveOptions {
|
||||
recursive: true,
|
||||
ignore_if_not_exists: true,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
log::info!("threads.db migrated to sqlite");
|
||||
Ok::<(), anyhow::Error>(())
|
||||
})
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ use std::path::{Path, PathBuf};
|
|||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use util::ResultExt;
|
||||
use util::rel_path::RelPath;
|
||||
|
||||
const RULES_FILE_NAMES: [&str; 9] = [
|
||||
".rules",
|
||||
|
|
@ -434,7 +435,7 @@ impl NativeAgent {
|
|||
cx: &mut App,
|
||||
) -> Task<(WorktreeContext, Option<RulesLoadingError>)> {
|
||||
let tree = worktree.read(cx);
|
||||
let root_name = tree.root_name().into();
|
||||
let root_name = tree.root_name_str().into();
|
||||
let abs_path = tree.abs_path();
|
||||
|
||||
let mut context = WorktreeContext {
|
||||
|
|
@ -474,7 +475,7 @@ impl NativeAgent {
|
|||
.into_iter()
|
||||
.filter_map(|name| {
|
||||
worktree
|
||||
.entry_for_path(name)
|
||||
.entry_for_path(RelPath::new(name).unwrap())
|
||||
.filter(|entry| entry.is_file())
|
||||
.map(|entry| entry.path.clone())
|
||||
})
|
||||
|
|
@ -558,7 +559,7 @@ impl NativeAgent {
|
|||
if items.iter().any(|(path, _, _)| {
|
||||
RULES_FILE_NAMES
|
||||
.iter()
|
||||
.any(|name| path.as_ref() == Path::new(name))
|
||||
.any(|name| path.as_ref() == RelPath::new(name).unwrap())
|
||||
}) {
|
||||
self.project_context_needs_refresh.send(()).ok();
|
||||
}
|
||||
|
|
@ -1208,7 +1209,7 @@ mod tests {
|
|||
use language_model::fake_provider::FakeLanguageModel;
|
||||
use serde_json::json;
|
||||
use settings::SettingsStore;
|
||||
use util::path;
|
||||
use util::{path, rel_path::rel_path};
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_maintaining_project_context(cx: &mut TestAppContext) {
|
||||
|
|
@ -1258,14 +1259,17 @@ mod tests {
|
|||
fs.insert_file("/a/.rules", Vec::new()).await;
|
||||
cx.run_until_parked();
|
||||
agent.read_with(cx, |agent, cx| {
|
||||
let rules_entry = worktree.read(cx).entry_for_path(".rules").unwrap();
|
||||
let rules_entry = worktree
|
||||
.read(cx)
|
||||
.entry_for_path(rel_path(".rules"))
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
agent.project_context.read(cx).worktrees,
|
||||
vec![WorktreeContext {
|
||||
root_name: "a".into(),
|
||||
abs_path: Path::new("/a").into(),
|
||||
rules_file: Some(RulesFileContext {
|
||||
path_in_worktree: Path::new(".rules").into(),
|
||||
path_in_worktree: rel_path(".rules").into(),
|
||||
text: "".into(),
|
||||
project_entry_id: rules_entry.id.to_usize()
|
||||
})
|
||||
|
|
|
|||
|
|
@ -422,17 +422,15 @@ mod tests {
|
|||
use agent::MessageSegment;
|
||||
use agent::context::LoadedContext;
|
||||
use client::Client;
|
||||
use fs::FakeFs;
|
||||
use fs::{FakeFs, Fs};
|
||||
use gpui::AppContext;
|
||||
use gpui::TestAppContext;
|
||||
use http_client::FakeHttpClient;
|
||||
use language_model::Role;
|
||||
use project::Project;
|
||||
use serde_json::json;
|
||||
use settings::SettingsStore;
|
||||
use util::test::TempTree;
|
||||
|
||||
fn init_test(cx: &mut TestAppContext) {
|
||||
fn init_test(fs: Arc<dyn Fs>, cx: &mut TestAppContext) {
|
||||
env_logger::try_init().ok();
|
||||
cx.update(|cx| {
|
||||
let settings_store = SettingsStore::test(cx);
|
||||
|
|
@ -443,7 +441,7 @@ mod tests {
|
|||
let http_client = FakeHttpClient::with_404_response();
|
||||
let clock = Arc::new(clock::FakeSystemClock::new());
|
||||
let client = Client::new(clock, http_client, cx);
|
||||
agent::init(cx);
|
||||
agent::init(fs, cx);
|
||||
agent_settings::init(cx);
|
||||
language_model::init(client, cx);
|
||||
});
|
||||
|
|
@ -451,10 +449,8 @@ mod tests {
|
|||
|
||||
#[gpui::test]
|
||||
async fn test_retrieving_old_thread(cx: &mut TestAppContext) {
|
||||
let tree = TempTree::new(json!({}));
|
||||
util::paths::set_home_dir(tree.path().into());
|
||||
init_test(cx);
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
init_test(fs.clone(), cx);
|
||||
let project = Project::test(fs, [], cx).await;
|
||||
|
||||
// Save a thread using the old agent.
|
||||
|
|
|
|||
|
|
@ -879,27 +879,11 @@ impl Thread {
|
|||
.map(|worktree| Self::worktree_snapshot(worktree, git_store.clone(), cx))
|
||||
.collect();
|
||||
|
||||
cx.spawn(async move |_, cx| {
|
||||
cx.spawn(async move |_, _| {
|
||||
let worktree_snapshots = futures::future::join_all(worktree_snapshots).await;
|
||||
|
||||
let mut unsaved_buffers = Vec::new();
|
||||
cx.update(|app_cx| {
|
||||
let buffer_store = project.read(app_cx).buffer_store();
|
||||
for buffer_handle in buffer_store.read(app_cx).buffers() {
|
||||
let buffer = buffer_handle.read(app_cx);
|
||||
if buffer.is_dirty()
|
||||
&& let Some(file) = buffer.file()
|
||||
{
|
||||
let path = file.path().to_string_lossy().to_string();
|
||||
unsaved_buffers.push(path);
|
||||
}
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
|
||||
Arc::new(ProjectSnapshot {
|
||||
worktree_snapshots,
|
||||
unsaved_buffer_paths: unsaved_buffers,
|
||||
timestamp: Utc::now(),
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -84,9 +84,7 @@ impl AgentTool for CopyPathTool {
|
|||
.and_then(|project_path| project.entry_for_path(&project_path, cx))
|
||||
{
|
||||
Some(entity) => match project.find_project_path(&input.destination_path, cx) {
|
||||
Some(project_path) => {
|
||||
project.copy_entry(entity.id, None, project_path.path, cx)
|
||||
}
|
||||
Some(project_path) => project.copy_entry(entity.id, project_path, cx),
|
||||
None => Task::ready(Err(anyhow!(
|
||||
"Destination path {} was outside the project.",
|
||||
input.destination_path
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use language::{DiagnosticSeverity, OffsetRangeExt};
|
|||
use project::Project;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{fmt::Write, path::Path, sync::Arc};
|
||||
use std::{fmt::Write, sync::Arc};
|
||||
use ui::SharedString;
|
||||
use util::markdown::MarkdownInlineCode;
|
||||
|
||||
|
|
@ -147,9 +147,7 @@ impl AgentTool for DiagnosticsTool {
|
|||
has_diagnostics = true;
|
||||
output.push_str(&format!(
|
||||
"{}: {} error(s), {} warning(s)\n",
|
||||
Path::new(worktree.read(cx).root_name())
|
||||
.join(project_path.path)
|
||||
.display(),
|
||||
worktree.read(cx).absolutize(&project_path.path).display(),
|
||||
summary.error_count,
|
||||
summary.warning_count
|
||||
));
|
||||
|
|
|
|||
|
|
@ -17,10 +17,12 @@ use schemars::JsonSchema;
|
|||
use serde::{Deserialize, Serialize};
|
||||
use settings::Settings;
|
||||
use smol::stream::StreamExt as _;
|
||||
use std::ffi::OsStr;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
use ui::SharedString;
|
||||
use util::ResultExt;
|
||||
use util::rel_path::RelPath;
|
||||
|
||||
const DEFAULT_UI_TEXT: &str = "Editing file";
|
||||
|
||||
|
|
@ -148,12 +150,11 @@ impl EditFileTool {
|
|||
|
||||
// If any path component matches the local settings folder, then this could affect
|
||||
// the editor in ways beyond the project source, so prompt.
|
||||
let local_settings_folder = paths::local_settings_folder_relative_path();
|
||||
let local_settings_folder = paths::local_settings_folder_name();
|
||||
let path = Path::new(&input.path);
|
||||
if path
|
||||
.components()
|
||||
.any(|component| component.as_os_str() == local_settings_folder.as_os_str())
|
||||
{
|
||||
if path.components().any(|component| {
|
||||
component.as_os_str() == <_ as AsRef<OsStr>>::as_ref(&local_settings_folder)
|
||||
}) {
|
||||
return event_stream.authorize(
|
||||
format!("{} (local settings)", input.display_description),
|
||||
cx,
|
||||
|
|
@ -162,6 +163,7 @@ impl EditFileTool {
|
|||
|
||||
// It's also possible that the global config dir is configured to be inside the project,
|
||||
// so check for that edge case too.
|
||||
// TODO this is broken when remoting
|
||||
if let Ok(canonical_path) = std::fs::canonicalize(&input.path)
|
||||
&& canonical_path.starts_with(paths::config_dir())
|
||||
{
|
||||
|
|
@ -216,9 +218,7 @@ impl AgentTool for EditFileTool {
|
|||
.read(cx)
|
||||
.short_full_path_for_project_path(&project_path, cx)
|
||||
})
|
||||
.unwrap_or(Path::new(&input.path).into())
|
||||
.to_string_lossy()
|
||||
.to_string()
|
||||
.unwrap_or(input.path.to_string_lossy().to_string())
|
||||
.into(),
|
||||
Err(raw_input) => {
|
||||
if let Some(input) =
|
||||
|
|
@ -235,9 +235,7 @@ impl AgentTool for EditFileTool {
|
|||
.read(cx)
|
||||
.short_full_path_for_project_path(&project_path, cx)
|
||||
})
|
||||
.unwrap_or(Path::new(&input.path).into())
|
||||
.to_string_lossy()
|
||||
.to_string()
|
||||
.unwrap_or(input.path)
|
||||
.into();
|
||||
}
|
||||
|
||||
|
|
@ -478,7 +476,7 @@ impl AgentTool for EditFileTool {
|
|||
) -> Result<()> {
|
||||
event_stream.update_diff(cx.new(|cx| {
|
||||
Diff::finalized(
|
||||
output.input_path,
|
||||
output.input_path.to_string_lossy().to_string(),
|
||||
Some(output.old_text.to_string()),
|
||||
output.new_text,
|
||||
self.language_registry.clone(),
|
||||
|
|
@ -542,10 +540,12 @@ fn resolve_path(
|
|||
let file_name = input
|
||||
.path
|
||||
.file_name()
|
||||
.and_then(|file_name| file_name.to_str())
|
||||
.and_then(|file_name| RelPath::new(file_name).ok())
|
||||
.context("Can't create file: invalid filename")?;
|
||||
|
||||
let new_file_path = parent_project_path.map(|parent| ProjectPath {
|
||||
path: Arc::from(parent.path.join(file_name)),
|
||||
path: parent.path.join(file_name),
|
||||
..parent
|
||||
});
|
||||
|
||||
|
|
@ -690,13 +690,10 @@ mod tests {
|
|||
cx.update(|cx| resolve_path(&input, project, cx))
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn assert_resolved_path_eq(path: anyhow::Result<ProjectPath>, expected: &str) {
|
||||
let actual = path
|
||||
.expect("Should return valid path")
|
||||
.path
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.replace("\\", "/"); // Naive Windows paths normalization
|
||||
let actual = path.expect("Should return valid path").path;
|
||||
let actual = actual.as_str();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
|
|
@ -1408,8 +1405,8 @@ mod tests {
|
|||
// Parent directory references - find_project_path resolves these
|
||||
(
|
||||
"project/../other",
|
||||
false,
|
||||
"Path with .. is resolved by find_project_path",
|
||||
true,
|
||||
"Path with .. that goes outside of root directory",
|
||||
),
|
||||
(
|
||||
"project/./src/file.rs",
|
||||
|
|
@ -1437,16 +1434,18 @@ mod tests {
|
|||
)
|
||||
});
|
||||
|
||||
cx.run_until_parked();
|
||||
|
||||
if should_confirm {
|
||||
stream_rx.expect_authorization().await;
|
||||
} else {
|
||||
auth.await.unwrap();
|
||||
assert!(
|
||||
stream_rx.try_next().is_err(),
|
||||
"Failed for case: {} - path: {} - expected no confirmation but got one",
|
||||
description,
|
||||
path
|
||||
);
|
||||
auth.await.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -156,10 +156,14 @@ impl AgentTool for FindPathTool {
|
|||
}
|
||||
|
||||
fn search_paths(glob: &str, project: Entity<Project>, cx: &mut App) -> Task<Result<Vec<PathBuf>>> {
|
||||
let path_matcher = match PathMatcher::new([
|
||||
// Sometimes models try to search for "". In this case, return all paths in the project.
|
||||
if glob.is_empty() { "*" } else { glob },
|
||||
]) {
|
||||
let path_style = project.read(cx).path_style(cx);
|
||||
let path_matcher = match PathMatcher::new(
|
||||
[
|
||||
// Sometimes models try to search for "". In this case, return all paths in the project.
|
||||
if glob.is_empty() { "*" } else { glob },
|
||||
],
|
||||
path_style,
|
||||
) {
|
||||
Ok(matcher) => matcher,
|
||||
Err(err) => return Task::ready(Err(anyhow!("Invalid glob: {err}"))),
|
||||
};
|
||||
|
|
@ -173,9 +177,8 @@ fn search_paths(glob: &str, project: Entity<Project>, cx: &mut App) -> Task<Resu
|
|||
let mut results = Vec::new();
|
||||
for snapshot in snapshots {
|
||||
for entry in snapshot.entries(false, 0) {
|
||||
let root_name = PathBuf::from(snapshot.root_name());
|
||||
if path_matcher.is_match(root_name.join(&entry.path)) {
|
||||
results.push(snapshot.abs_path().join(entry.path.as_ref()));
|
||||
if path_matcher.is_match(snapshot.root_name().join(&entry.path).as_std_path()) {
|
||||
results.push(snapshot.absolutize(&entry.path));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -110,12 +110,15 @@ impl AgentTool for GrepTool {
|
|||
const CONTEXT_LINES: u32 = 2;
|
||||
const MAX_ANCESTOR_LINES: u32 = 10;
|
||||
|
||||
let path_style = self.project.read(cx).path_style(cx);
|
||||
|
||||
let include_matcher = match PathMatcher::new(
|
||||
input
|
||||
.include_pattern
|
||||
.as_ref()
|
||||
.into_iter()
|
||||
.collect::<Vec<_>>(),
|
||||
path_style,
|
||||
) {
|
||||
Ok(matcher) => matcher,
|
||||
Err(error) => {
|
||||
|
|
@ -132,7 +135,7 @@ impl AgentTool for GrepTool {
|
|||
.iter()
|
||||
.chain(global_settings.private_files.sources().iter());
|
||||
|
||||
match PathMatcher::new(exclude_patterns) {
|
||||
match PathMatcher::new(exclude_patterns, path_style) {
|
||||
Ok(matcher) => matcher,
|
||||
Err(error) => {
|
||||
return Task::ready(Err(anyhow!("invalid exclude pattern: {error}")));
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@ use crate::{AgentTool, ToolCallEventStream};
|
|||
use agent_client_protocol::ToolKind;
|
||||
use anyhow::{Result, anyhow};
|
||||
use gpui::{App, Entity, SharedString, Task};
|
||||
use project::{Project, WorktreeSettings};
|
||||
use project::{Project, ProjectPath, WorktreeSettings};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::Settings;
|
||||
use std::fmt::Write;
|
||||
use std::{path::Path, sync::Arc};
|
||||
use std::sync::Arc;
|
||||
use util::markdown::MarkdownInlineCode;
|
||||
|
||||
/// Lists files and directories in a given path. Prefer the `grep` or `find_path` tools when searching the codebase.
|
||||
|
|
@ -86,13 +86,13 @@ impl AgentTool for ListDirectoryTool {
|
|||
.read(cx)
|
||||
.worktrees(cx)
|
||||
.filter_map(|worktree| {
|
||||
worktree.read(cx).root_entry().and_then(|entry| {
|
||||
if entry.is_dir() {
|
||||
entry.path.to_str()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
let worktree = worktree.read(cx);
|
||||
let root_entry = worktree.root_entry()?;
|
||||
if root_entry.is_dir() {
|
||||
Some(root_entry.path.display(worktree.path_style()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
|
|
@ -143,7 +143,7 @@ impl AgentTool for ListDirectoryTool {
|
|||
}
|
||||
|
||||
let worktree_snapshot = worktree.read(cx).snapshot();
|
||||
let worktree_root_name = worktree.read(cx).root_name().to_string();
|
||||
let worktree_root_name = worktree.read(cx).root_name();
|
||||
|
||||
let Some(entry) = worktree_snapshot.entry_for_path(&project_path.path) else {
|
||||
return Task::ready(Err(anyhow!("Path not found: {}", input.path)));
|
||||
|
|
@ -165,25 +165,17 @@ impl AgentTool for ListDirectoryTool {
|
|||
continue;
|
||||
}
|
||||
|
||||
if self
|
||||
.project
|
||||
.read(cx)
|
||||
.find_project_path(&entry.path, cx)
|
||||
.map(|project_path| {
|
||||
let worktree_settings = WorktreeSettings::get(Some((&project_path).into()), cx);
|
||||
|
||||
worktree_settings.is_path_excluded(&project_path.path)
|
||||
|| worktree_settings.is_path_private(&project_path.path)
|
||||
})
|
||||
.unwrap_or(false)
|
||||
let project_path: ProjectPath = (worktree_snapshot.id(), entry.path.clone()).into();
|
||||
if worktree_settings.is_path_excluded(&project_path.path)
|
||||
|| worktree_settings.is_path_private(&project_path.path)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let full_path = Path::new(&worktree_root_name)
|
||||
let full_path = worktree_root_name
|
||||
.join(&entry.path)
|
||||
.display()
|
||||
.to_string();
|
||||
.display(worktree_snapshot.path_style())
|
||||
.into_owned();
|
||||
if entry.is_dir() {
|
||||
folders.push(full_path);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ impl AgentTool for MovePathTool {
|
|||
.and_then(|project_path| project.entry_for_path(&project_path, cx))
|
||||
{
|
||||
Some(entity) => match project.find_project_path(&input.destination_path, cx) {
|
||||
Some(project_path) => project.rename_entry(entity.id, project_path.path, cx),
|
||||
Some(project_path) => project.rename_entry(entity.id, project_path, cx),
|
||||
None => Task::ready(Err(anyhow!(
|
||||
"Destination path {} was outside the project.",
|
||||
input.destination_path
|
||||
|
|
|
|||
|
|
@ -82,12 +82,12 @@ impl AgentTool for ReadFileTool {
|
|||
{
|
||||
match (input.start_line, input.end_line) {
|
||||
(Some(start), Some(end)) => {
|
||||
format!("Read file `{}` (lines {}-{})", path.display(), start, end,)
|
||||
format!("Read file `{path}` (lines {}-{})", start, end,)
|
||||
}
|
||||
(Some(start), None) => {
|
||||
format!("Read file `{}` (from line {})", path.display(), start)
|
||||
format!("Read file `{path}` (from line {})", start)
|
||||
}
|
||||
_ => format!("Read file `{}`", path.display()),
|
||||
_ => format!("Read file `{path}`"),
|
||||
}
|
||||
.into()
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use std::cell::RefCell;
|
||||
use std::ops::Range;
|
||||
use std::path::PathBuf;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
|
|
@ -13,7 +14,7 @@ use fuzzy::{StringMatch, StringMatchCandidate};
|
|||
use gpui::{App, Entity, Task, WeakEntity};
|
||||
use language::{Buffer, CodeLabel, HighlightId};
|
||||
use lsp::CompletionContext;
|
||||
use project::lsp_store::CompletionDocumentation;
|
||||
use project::lsp_store::{CompletionDocumentation, SymbolLocation};
|
||||
use project::{
|
||||
Completion, CompletionDisplayOptions, CompletionIntent, CompletionResponse, Project,
|
||||
ProjectPath, Symbol, WorktreeId,
|
||||
|
|
@ -22,6 +23,7 @@ use prompt_store::PromptStore;
|
|||
use rope::Point;
|
||||
use text::{Anchor, ToPoint as _};
|
||||
use ui::prelude::*;
|
||||
use util::rel_path::RelPath;
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::AgentPanel;
|
||||
|
|
@ -187,7 +189,7 @@ impl ContextPickerCompletionProvider {
|
|||
|
||||
pub(crate) fn completion_for_path(
|
||||
project_path: ProjectPath,
|
||||
path_prefix: &str,
|
||||
path_prefix: &RelPath,
|
||||
is_recent: bool,
|
||||
is_directory: bool,
|
||||
source_range: Range<Anchor>,
|
||||
|
|
@ -195,10 +197,12 @@ impl ContextPickerCompletionProvider {
|
|||
project: Entity<Project>,
|
||||
cx: &mut App,
|
||||
) -> Option<Completion> {
|
||||
let path_style = project.read(cx).path_style(cx);
|
||||
let (file_name, directory) =
|
||||
crate::context_picker::file_context_picker::extract_file_name_and_directory(
|
||||
&project_path.path,
|
||||
path_prefix,
|
||||
path_style,
|
||||
);
|
||||
|
||||
let label =
|
||||
|
|
@ -250,7 +254,15 @@ impl ContextPickerCompletionProvider {
|
|||
|
||||
let label = CodeLabel::plain(symbol.name.clone(), None);
|
||||
|
||||
let abs_path = project.read(cx).absolute_path(&symbol.path, cx)?;
|
||||
let abs_path = match &symbol.path {
|
||||
SymbolLocation::InProject(project_path) => {
|
||||
project.read(cx).absolute_path(&project_path, cx)?
|
||||
}
|
||||
SymbolLocation::OutsideProject {
|
||||
abs_path,
|
||||
signature: _,
|
||||
} => PathBuf::from(abs_path.as_ref()),
|
||||
};
|
||||
let uri = MentionUri::Symbol {
|
||||
abs_path,
|
||||
name: symbol.name.clone(),
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ use std::{
|
|||
use text::OffsetRangeExt;
|
||||
use theme::ThemeSettings;
|
||||
use ui::{ButtonLike, TintColor, Toggleable, prelude::*};
|
||||
use util::{ResultExt, debug_panic};
|
||||
use util::{ResultExt, debug_panic, paths::PathStyle, rel_path::RelPath};
|
||||
use workspace::{Workspace, notifications::NotifyResultExt as _};
|
||||
use zed_actions::agent::Chat;
|
||||
|
||||
|
|
@ -108,6 +108,11 @@ impl MessageEditor {
|
|||
available_commands.clone(),
|
||||
));
|
||||
let mention_set = MentionSet::default();
|
||||
// TODO: fix mentions when remoting with mixed path styles.
|
||||
let host_and_guest_paths_differ = project
|
||||
.read(cx)
|
||||
.remote_client()
|
||||
.is_some_and(|client| client.read(cx).path_style() != PathStyle::local());
|
||||
let editor = cx.new(|cx| {
|
||||
let buffer = cx.new(|cx| Buffer::local("", cx).with_language(Arc::new(language), cx));
|
||||
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
|
||||
|
|
@ -117,7 +122,9 @@ impl MessageEditor {
|
|||
editor.set_show_indent_guides(false, cx);
|
||||
editor.set_soft_wrap();
|
||||
editor.set_use_modal_editing(true);
|
||||
editor.set_completion_provider(Some(completion_provider.clone()));
|
||||
if !host_and_guest_paths_differ {
|
||||
editor.set_completion_provider(Some(completion_provider.clone()));
|
||||
}
|
||||
editor.set_context_menu_options(ContextMenuOptions {
|
||||
min_entries_visible: 12,
|
||||
max_entries_visible: 12,
|
||||
|
|
@ -947,6 +954,7 @@ impl MessageEditor {
|
|||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let path_style = self.project.read(cx).path_style(cx);
|
||||
let buffer = self.editor.read(cx).buffer().clone();
|
||||
let Some(buffer) = buffer.read(cx).as_singleton() else {
|
||||
return;
|
||||
|
|
@ -956,18 +964,15 @@ impl MessageEditor {
|
|||
let Some(entry) = self.project.read(cx).entry_for_path(&path, cx) else {
|
||||
continue;
|
||||
};
|
||||
let Some(abs_path) = self.project.read(cx).absolute_path(&path, cx) else {
|
||||
let Some(worktree) = self.project.read(cx).worktree_for_id(path.worktree_id, cx) else {
|
||||
continue;
|
||||
};
|
||||
let path_prefix = abs_path
|
||||
.file_name()
|
||||
.unwrap_or(path.path.as_os_str())
|
||||
.display()
|
||||
.to_string();
|
||||
let abs_path = worktree.read(cx).absolutize(&path.path);
|
||||
let (file_name, _) =
|
||||
crate::context_picker::file_context_picker::extract_file_name_and_directory(
|
||||
&path.path,
|
||||
&path_prefix,
|
||||
worktree.read(cx).root_name(),
|
||||
path_style,
|
||||
);
|
||||
|
||||
let uri = if entry.is_dir() {
|
||||
|
|
@ -1176,7 +1181,7 @@ fn full_mention_for_directory(
|
|||
abs_path: &Path,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<Mention>> {
|
||||
fn collect_files_in_path(worktree: &Worktree, path: &Path) -> Vec<(Arc<Path>, PathBuf)> {
|
||||
fn collect_files_in_path(worktree: &Worktree, path: &RelPath) -> Vec<(Arc<RelPath>, PathBuf)> {
|
||||
let mut files = Vec::new();
|
||||
|
||||
for entry in worktree.child_entries(path) {
|
||||
|
|
@ -1261,7 +1266,7 @@ fn full_mention_for_directory(
|
|||
})
|
||||
}
|
||||
|
||||
fn render_directory_contents(entries: Vec<(Arc<Path>, PathBuf, String)>) -> String {
|
||||
fn render_directory_contents(entries: Vec<(Arc<RelPath>, PathBuf, String)>) -> String {
|
||||
let mut output = String::new();
|
||||
for (_relative_path, full_path, content) in entries {
|
||||
let fence = codeblock_fence_for_path(Some(&full_path), None);
|
||||
|
|
@ -1595,7 +1600,7 @@ mod tests {
|
|||
use serde_json::json;
|
||||
use text::Point;
|
||||
use ui::{App, Context, IntoElement, Render, SharedString, Window};
|
||||
use util::{path, uri};
|
||||
use util::{path, paths::PathStyle, rel_path::rel_path, uri};
|
||||
use workspace::{AppState, Item, Workspace};
|
||||
|
||||
use crate::acp::{
|
||||
|
|
@ -2105,16 +2110,18 @@ mod tests {
|
|||
let mut cx = VisualTestContext::from_window(*window, cx);
|
||||
|
||||
let paths = vec![
|
||||
path!("a/one.txt"),
|
||||
path!("a/two.txt"),
|
||||
path!("a/three.txt"),
|
||||
path!("a/four.txt"),
|
||||
path!("b/five.txt"),
|
||||
path!("b/six.txt"),
|
||||
path!("b/seven.txt"),
|
||||
path!("b/eight.txt"),
|
||||
rel_path("a/one.txt"),
|
||||
rel_path("a/two.txt"),
|
||||
rel_path("a/three.txt"),
|
||||
rel_path("a/four.txt"),
|
||||
rel_path("b/five.txt"),
|
||||
rel_path("b/six.txt"),
|
||||
rel_path("b/seven.txt"),
|
||||
rel_path("b/eight.txt"),
|
||||
];
|
||||
|
||||
let slash = PathStyle::local().separator();
|
||||
|
||||
let mut opened_editors = Vec::new();
|
||||
for path in paths {
|
||||
let buffer = workspace
|
||||
|
|
@ -2122,7 +2129,7 @@ mod tests {
|
|||
workspace.open_path(
|
||||
ProjectPath {
|
||||
worktree_id,
|
||||
path: Path::new(path).into(),
|
||||
path: path.into(),
|
||||
},
|
||||
None,
|
||||
false,
|
||||
|
|
@ -2183,10 +2190,10 @@ mod tests {
|
|||
assert_eq!(
|
||||
current_completion_labels(editor),
|
||||
&[
|
||||
"eight.txt dir/b/",
|
||||
"seven.txt dir/b/",
|
||||
"six.txt dir/b/",
|
||||
"five.txt dir/b/",
|
||||
format!("eight.txt dir{slash}b{slash}"),
|
||||
format!("seven.txt dir{slash}b{slash}"),
|
||||
format!("six.txt dir{slash}b{slash}"),
|
||||
format!("five.txt dir{slash}b{slash}"),
|
||||
]
|
||||
);
|
||||
editor.set_text("", window, cx);
|
||||
|
|
@ -2214,14 +2221,14 @@ mod tests {
|
|||
assert_eq!(
|
||||
current_completion_labels(editor),
|
||||
&[
|
||||
"eight.txt dir/b/",
|
||||
"seven.txt dir/b/",
|
||||
"six.txt dir/b/",
|
||||
"five.txt dir/b/",
|
||||
"Files & Directories",
|
||||
"Symbols",
|
||||
"Threads",
|
||||
"Fetch"
|
||||
format!("eight.txt dir{slash}b{slash}"),
|
||||
format!("seven.txt dir{slash}b{slash}"),
|
||||
format!("six.txt dir{slash}b{slash}"),
|
||||
format!("five.txt dir{slash}b{slash}"),
|
||||
"Files & Directories".into(),
|
||||
"Symbols".into(),
|
||||
"Threads".into(),
|
||||
"Fetch".into()
|
||||
]
|
||||
);
|
||||
});
|
||||
|
|
@ -2248,7 +2255,10 @@ mod tests {
|
|||
editor.update(&mut cx, |editor, cx| {
|
||||
assert_eq!(editor.text(cx), "Lorem @file one");
|
||||
assert!(editor.has_visible_completions_menu());
|
||||
assert_eq!(current_completion_labels(editor), vec!["one.txt dir/a/"]);
|
||||
assert_eq!(
|
||||
current_completion_labels(editor),
|
||||
vec![format!("one.txt dir{slash}a{slash}")]
|
||||
);
|
||||
});
|
||||
|
||||
editor.update_in(&mut cx, |editor, window, cx| {
|
||||
|
|
@ -2505,7 +2515,7 @@ mod tests {
|
|||
format!("Lorem [@one.txt]({url_one}) Ipsum [@eight.txt]({url_eight}) [@MySymbol]({url_one}?symbol=MySymbol#L1:1) @file x.png")
|
||||
);
|
||||
assert!(editor.has_visible_completions_menu());
|
||||
assert_eq!(current_completion_labels(editor), &["x.png dir/"]);
|
||||
assert_eq!(current_completion_labels(editor), &[format!("x.png dir{slash}")]);
|
||||
});
|
||||
|
||||
editor.update_in(&mut cx, |editor, window, cx| {
|
||||
|
|
@ -2544,7 +2554,7 @@ mod tests {
|
|||
format!("Lorem [@one.txt]({url_one}) Ipsum [@eight.txt]({url_eight}) [@MySymbol]({url_one}?symbol=MySymbol#L1:1) @file x.png")
|
||||
);
|
||||
assert!(editor.has_visible_completions_menu());
|
||||
assert_eq!(current_completion_labels(editor), &["x.png dir/"]);
|
||||
assert_eq!(current_completion_labels(editor), &[format!("x.png dir{slash}")]);
|
||||
});
|
||||
|
||||
editor.update_in(&mut cx, |editor, window, cx| {
|
||||
|
|
|
|||
|
|
@ -3704,29 +3704,32 @@ impl AcpThreadView {
|
|||
|(index, (buffer, _diff))| {
|
||||
let file = buffer.read(cx).file()?;
|
||||
let path = file.path();
|
||||
let path_style = file.path_style(cx);
|
||||
let separator = file.path_style(cx).separator();
|
||||
|
||||
let file_path = path.parent().and_then(|parent| {
|
||||
let parent_str = parent.to_string_lossy();
|
||||
|
||||
if parent_str.is_empty() {
|
||||
if parent.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(
|
||||
Label::new(format!("/{}{}", parent_str, std::path::MAIN_SEPARATOR_STR))
|
||||
.color(Color::Muted)
|
||||
.size(LabelSize::XSmall)
|
||||
.buffer_font(cx),
|
||||
Label::new(format!(
|
||||
"{separator}{}{separator}",
|
||||
parent.display(path_style)
|
||||
))
|
||||
.color(Color::Muted)
|
||||
.size(LabelSize::XSmall)
|
||||
.buffer_font(cx),
|
||||
)
|
||||
}
|
||||
});
|
||||
|
||||
let file_name = path.file_name().map(|name| {
|
||||
Label::new(name.to_string_lossy().to_string())
|
||||
Label::new(name.to_string())
|
||||
.size(LabelSize::XSmall)
|
||||
.buffer_font(cx)
|
||||
});
|
||||
|
||||
let file_icon = FileIcons::get_icon(path, cx)
|
||||
let file_icon = FileIcons::get_icon(path.as_std_path(), cx)
|
||||
.map(Icon::from_path)
|
||||
.map(|icon| icon.color(Color::Muted).size(IconSize::Small))
|
||||
.unwrap_or_else(|| {
|
||||
|
|
@ -4569,7 +4572,7 @@ impl AcpThreadView {
|
|||
.read(cx)
|
||||
.visible_worktrees(cx)
|
||||
.next()
|
||||
.map(|worktree| worktree.read(cx).root_name().to_string())
|
||||
.map(|worktree| worktree.read(cx).root_name_str().to_string())
|
||||
});
|
||||
|
||||
if let Some(screen_window) = cx
|
||||
|
|
|
|||
|
|
@ -264,7 +264,7 @@ pub fn init(
|
|||
init_language_model_settings(cx);
|
||||
}
|
||||
assistant_slash_command::init(cx);
|
||||
agent::init(cx);
|
||||
agent::init(fs.clone(), cx);
|
||||
agent_panel::init(cx);
|
||||
context_server_configuration::init(language_registry.clone(), fs.clone(), cx);
|
||||
TextThreadEditor::init(cx);
|
||||
|
|
|
|||
|
|
@ -33,6 +33,8 @@ use thread_context_picker::{
|
|||
use ui::{
|
||||
ButtonLike, ContextMenu, ContextMenuEntry, ContextMenuItem, Disclosure, TintColor, prelude::*,
|
||||
};
|
||||
use util::paths::PathStyle;
|
||||
use util::rel_path::RelPath;
|
||||
use workspace::{Workspace, notifications::NotifyResultExt};
|
||||
|
||||
use agent::{
|
||||
|
|
@ -228,12 +230,19 @@ impl ContextPicker {
|
|||
let context_picker = cx.entity();
|
||||
|
||||
let menu = ContextMenu::build(window, cx, move |menu, _window, cx| {
|
||||
let Some(workspace) = self.workspace.upgrade() else {
|
||||
return menu;
|
||||
};
|
||||
let path_style = workspace.read(cx).path_style(cx);
|
||||
let recent = self.recent_entries(cx);
|
||||
let has_recent = !recent.is_empty();
|
||||
let recent_entries = recent
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(ix, entry)| self.recent_menu_item(context_picker.clone(), ix, entry));
|
||||
.map(|(ix, entry)| {
|
||||
self.recent_menu_item(context_picker.clone(), ix, entry, path_style)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let entries = self
|
||||
.workspace
|
||||
|
|
@ -395,6 +404,7 @@ impl ContextPicker {
|
|||
context_picker: Entity<ContextPicker>,
|
||||
ix: usize,
|
||||
entry: RecentEntry,
|
||||
path_style: PathStyle,
|
||||
) -> ContextMenuItem {
|
||||
match entry {
|
||||
RecentEntry::File {
|
||||
|
|
@ -413,6 +423,7 @@ impl ContextPicker {
|
|||
&path,
|
||||
&path_prefix,
|
||||
false,
|
||||
path_style,
|
||||
context_store.clone(),
|
||||
cx,
|
||||
)
|
||||
|
|
@ -586,7 +597,7 @@ impl Render for ContextPicker {
|
|||
pub(crate) enum RecentEntry {
|
||||
File {
|
||||
project_path: ProjectPath,
|
||||
path_prefix: Arc<str>,
|
||||
path_prefix: Arc<RelPath>,
|
||||
},
|
||||
Thread(ThreadContextEntry),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ use http_client::HttpClientWithUrl;
|
|||
use itertools::Itertools;
|
||||
use language::{Buffer, CodeLabel, HighlightId};
|
||||
use lsp::CompletionContext;
|
||||
use project::lsp_store::SymbolLocation;
|
||||
use project::{
|
||||
Completion, CompletionDisplayOptions, CompletionIntent, CompletionResponse, ProjectPath,
|
||||
Symbol, WorktreeId,
|
||||
|
|
@ -22,6 +23,8 @@ use rope::Point;
|
|||
use text::{Anchor, OffsetRangeExt, ToPoint};
|
||||
use ui::prelude::*;
|
||||
use util::ResultExt as _;
|
||||
use util::paths::PathStyle;
|
||||
use util::rel_path::RelPath;
|
||||
use workspace::Workspace;
|
||||
|
||||
use agent::{
|
||||
|
|
@ -574,11 +577,12 @@ impl ContextPickerCompletionProvider {
|
|||
|
||||
fn completion_for_path(
|
||||
project_path: ProjectPath,
|
||||
path_prefix: &str,
|
||||
path_prefix: &RelPath,
|
||||
is_recent: bool,
|
||||
is_directory: bool,
|
||||
excerpt_id: ExcerptId,
|
||||
source_range: Range<Anchor>,
|
||||
path_style: PathStyle,
|
||||
editor: Entity<Editor>,
|
||||
context_store: Entity<ContextStore>,
|
||||
cx: &App,
|
||||
|
|
@ -586,6 +590,7 @@ impl ContextPickerCompletionProvider {
|
|||
let (file_name, directory) = super::file_context_picker::extract_file_name_and_directory(
|
||||
&project_path.path,
|
||||
path_prefix,
|
||||
path_style,
|
||||
);
|
||||
|
||||
let label =
|
||||
|
|
@ -657,17 +662,22 @@ impl ContextPickerCompletionProvider {
|
|||
workspace: Entity<Workspace>,
|
||||
cx: &mut App,
|
||||
) -> Option<Completion> {
|
||||
let path_style = workspace.read(cx).path_style(cx);
|
||||
let SymbolLocation::InProject(symbol_path) = &symbol.path else {
|
||||
return None;
|
||||
};
|
||||
let path_prefix = workspace
|
||||
.read(cx)
|
||||
.project()
|
||||
.read(cx)
|
||||
.worktree_for_id(symbol.path.worktree_id, cx)?
|
||||
.worktree_for_id(symbol_path.worktree_id, cx)?
|
||||
.read(cx)
|
||||
.root_name();
|
||||
|
||||
let (file_name, directory) = super::file_context_picker::extract_file_name_and_directory(
|
||||
&symbol.path.path,
|
||||
&symbol_path.path,
|
||||
path_prefix,
|
||||
path_style,
|
||||
);
|
||||
let full_path = if let Some(directory) = directory {
|
||||
format!("{}{}", directory, file_name)
|
||||
|
|
@ -768,6 +778,7 @@ impl CompletionProvider for ContextPickerCompletionProvider {
|
|||
let text_thread_store = self.text_thread_store.clone();
|
||||
let editor = self.editor.clone();
|
||||
let http_client = workspace.read(cx).client().http_client();
|
||||
let path_style = workspace.read(cx).path_style(cx);
|
||||
|
||||
let MentionCompletion { mode, argument, .. } = state;
|
||||
let query = argument.unwrap_or_else(|| "".to_string());
|
||||
|
|
@ -834,6 +845,7 @@ impl CompletionProvider for ContextPickerCompletionProvider {
|
|||
mat.is_dir,
|
||||
excerpt_id,
|
||||
source_range.clone(),
|
||||
path_style,
|
||||
editor.clone(),
|
||||
context_store.clone(),
|
||||
cx,
|
||||
|
|
@ -1064,7 +1076,7 @@ mod tests {
|
|||
use serde_json::json;
|
||||
use settings::SettingsStore;
|
||||
use std::{ops::Deref, rc::Rc};
|
||||
use util::path;
|
||||
use util::{path, rel_path::rel_path};
|
||||
use workspace::{AppState, Item};
|
||||
|
||||
#[test]
|
||||
|
|
@ -1215,16 +1227,18 @@ mod tests {
|
|||
let mut cx = VisualTestContext::from_window(*window.deref(), cx);
|
||||
|
||||
let paths = vec![
|
||||
path!("a/one.txt"),
|
||||
path!("a/two.txt"),
|
||||
path!("a/three.txt"),
|
||||
path!("a/four.txt"),
|
||||
path!("b/five.txt"),
|
||||
path!("b/six.txt"),
|
||||
path!("b/seven.txt"),
|
||||
path!("b/eight.txt"),
|
||||
rel_path("a/one.txt"),
|
||||
rel_path("a/two.txt"),
|
||||
rel_path("a/three.txt"),
|
||||
rel_path("a/four.txt"),
|
||||
rel_path("b/five.txt"),
|
||||
rel_path("b/six.txt"),
|
||||
rel_path("b/seven.txt"),
|
||||
rel_path("b/eight.txt"),
|
||||
];
|
||||
|
||||
let slash = PathStyle::local().separator();
|
||||
|
||||
let mut opened_editors = Vec::new();
|
||||
for path in paths {
|
||||
let buffer = workspace
|
||||
|
|
@ -1232,7 +1246,7 @@ mod tests {
|
|||
workspace.open_path(
|
||||
ProjectPath {
|
||||
worktree_id,
|
||||
path: Path::new(path).into(),
|
||||
path: path.into(),
|
||||
},
|
||||
None,
|
||||
false,
|
||||
|
|
@ -1308,13 +1322,13 @@ mod tests {
|
|||
assert_eq!(
|
||||
current_completion_labels(editor),
|
||||
&[
|
||||
"seven.txt dir/b/",
|
||||
"six.txt dir/b/",
|
||||
"five.txt dir/b/",
|
||||
"four.txt dir/a/",
|
||||
"Files & Directories",
|
||||
"Symbols",
|
||||
"Fetch"
|
||||
format!("seven.txt dir{slash}b{slash}"),
|
||||
format!("six.txt dir{slash}b{slash}"),
|
||||
format!("five.txt dir{slash}b{slash}"),
|
||||
format!("four.txt dir{slash}a{slash}"),
|
||||
"Files & Directories".into(),
|
||||
"Symbols".into(),
|
||||
"Fetch".into()
|
||||
]
|
||||
);
|
||||
});
|
||||
|
|
@ -1341,7 +1355,10 @@ mod tests {
|
|||
editor.update(&mut cx, |editor, cx| {
|
||||
assert_eq!(editor.text(cx), "Lorem @file one");
|
||||
assert!(editor.has_visible_completions_menu());
|
||||
assert_eq!(current_completion_labels(editor), vec!["one.txt dir/a/"]);
|
||||
assert_eq!(
|
||||
current_completion_labels(editor),
|
||||
vec![format!("one.txt dir{slash}a{slash}")]
|
||||
);
|
||||
});
|
||||
|
||||
editor.update_in(&mut cx, |editor, window, cx| {
|
||||
|
|
@ -1350,7 +1367,10 @@ mod tests {
|
|||
});
|
||||
|
||||
editor.update(&mut cx, |editor, cx| {
|
||||
assert_eq!(editor.text(cx), "Lorem [@one.txt](@file:dir/a/one.txt) ");
|
||||
assert_eq!(
|
||||
editor.text(cx),
|
||||
format!("Lorem [@one.txt](@file:dir{slash}a{slash}one.txt) ")
|
||||
);
|
||||
assert!(!editor.has_visible_completions_menu());
|
||||
assert_eq!(
|
||||
fold_ranges(editor, cx),
|
||||
|
|
@ -1361,7 +1381,10 @@ mod tests {
|
|||
cx.simulate_input(" ");
|
||||
|
||||
editor.update(&mut cx, |editor, cx| {
|
||||
assert_eq!(editor.text(cx), "Lorem [@one.txt](@file:dir/a/one.txt) ");
|
||||
assert_eq!(
|
||||
editor.text(cx),
|
||||
format!("Lorem [@one.txt](@file:dir{slash}a{slash}one.txt) ")
|
||||
);
|
||||
assert!(!editor.has_visible_completions_menu());
|
||||
assert_eq!(
|
||||
fold_ranges(editor, cx),
|
||||
|
|
@ -1374,7 +1397,7 @@ mod tests {
|
|||
editor.update(&mut cx, |editor, cx| {
|
||||
assert_eq!(
|
||||
editor.text(cx),
|
||||
"Lorem [@one.txt](@file:dir/a/one.txt) Ipsum ",
|
||||
format!("Lorem [@one.txt](@file:dir{slash}a{slash}one.txt) Ipsum "),
|
||||
);
|
||||
assert!(!editor.has_visible_completions_menu());
|
||||
assert_eq!(
|
||||
|
|
@ -1388,7 +1411,7 @@ mod tests {
|
|||
editor.update(&mut cx, |editor, cx| {
|
||||
assert_eq!(
|
||||
editor.text(cx),
|
||||
"Lorem [@one.txt](@file:dir/a/one.txt) Ipsum @file ",
|
||||
format!("Lorem [@one.txt](@file:dir{slash}a{slash}one.txt) Ipsum @file "),
|
||||
);
|
||||
assert!(editor.has_visible_completions_menu());
|
||||
assert_eq!(
|
||||
|
|
@ -1406,7 +1429,7 @@ mod tests {
|
|||
editor.update(&mut cx, |editor, cx| {
|
||||
assert_eq!(
|
||||
editor.text(cx),
|
||||
"Lorem [@one.txt](@file:dir/a/one.txt) Ipsum [@seven.txt](@file:dir/b/seven.txt) "
|
||||
format!("Lorem [@one.txt](@file:dir{slash}a{slash}one.txt) Ipsum [@seven.txt](@file:dir{slash}b{slash}seven.txt) ")
|
||||
);
|
||||
assert!(!editor.has_visible_completions_menu());
|
||||
assert_eq!(
|
||||
|
|
@ -1423,7 +1446,7 @@ mod tests {
|
|||
editor.update(&mut cx, |editor, cx| {
|
||||
assert_eq!(
|
||||
editor.text(cx),
|
||||
"Lorem [@one.txt](@file:dir/a/one.txt) Ipsum [@seven.txt](@file:dir/b/seven.txt) \n@"
|
||||
format!("Lorem [@one.txt](@file:dir{slash}a{slash}one.txt) Ipsum [@seven.txt](@file:dir{slash}b{slash}seven.txt) \n@")
|
||||
);
|
||||
assert!(editor.has_visible_completions_menu());
|
||||
assert_eq!(
|
||||
|
|
@ -1444,7 +1467,7 @@ mod tests {
|
|||
editor.update(&mut cx, |editor, cx| {
|
||||
assert_eq!(
|
||||
editor.text(cx),
|
||||
"Lorem [@one.txt](@file:dir/a/one.txt) Ipsum [@seven.txt](@file:dir/b/seven.txt) \n[@six.txt](@file:dir/b/six.txt) "
|
||||
format!("Lorem [@one.txt](@file:dir{slash}a{slash}one.txt) Ipsum [@seven.txt](@file:dir{slash}b{slash}seven.txt) \n[@six.txt](@file:dir{slash}b{slash}six.txt) ")
|
||||
);
|
||||
assert!(!editor.has_visible_completions_menu());
|
||||
assert_eq!(
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
|
||||
|
|
@ -10,7 +9,7 @@ use gpui::{
|
|||
use picker::{Picker, PickerDelegate};
|
||||
use project::{PathMatchCandidateSet, ProjectPath, WorktreeId};
|
||||
use ui::{ListItem, Tooltip, prelude::*};
|
||||
use util::ResultExt as _;
|
||||
use util::{ResultExt as _, paths::PathStyle, rel_path::RelPath};
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::context_picker::ContextPicker;
|
||||
|
|
@ -161,6 +160,8 @@ impl PickerDelegate for FileContextPickerDelegate {
|
|||
cx: &mut Context<Picker<Self>>,
|
||||
) -> Option<Self::ListItem> {
|
||||
let FileMatch { mat, .. } = &self.matches.get(ix)?;
|
||||
let workspace = self.workspace.upgrade()?;
|
||||
let path_style = workspace.read(cx).path_style(cx);
|
||||
|
||||
Some(
|
||||
ListItem::new(ix)
|
||||
|
|
@ -172,6 +173,7 @@ impl PickerDelegate for FileContextPickerDelegate {
|
|||
&mat.path,
|
||||
&mat.path_prefix,
|
||||
mat.is_dir,
|
||||
path_style,
|
||||
self.context_store.clone(),
|
||||
cx,
|
||||
)),
|
||||
|
|
@ -214,14 +216,13 @@ pub(crate) fn search_files(
|
|||
|
||||
let file_matches = project.worktrees(cx).flat_map(|worktree| {
|
||||
let worktree = worktree.read(cx);
|
||||
let path_prefix: Arc<str> = worktree.root_name().into();
|
||||
worktree.entries(false, 0).map(move |entry| FileMatch {
|
||||
mat: PathMatch {
|
||||
score: 0.,
|
||||
positions: Vec::new(),
|
||||
worktree_id: worktree.id().to_usize(),
|
||||
path: entry.path.clone(),
|
||||
path_prefix: path_prefix.clone(),
|
||||
path_prefix: worktree.root_name().into(),
|
||||
distance_to_relative_ancestor: 0,
|
||||
is_dir: entry.is_dir(),
|
||||
},
|
||||
|
|
@ -269,51 +270,31 @@ pub(crate) fn search_files(
|
|||
}
|
||||
|
||||
pub fn extract_file_name_and_directory(
|
||||
path: &Path,
|
||||
path_prefix: &str,
|
||||
path: &RelPath,
|
||||
path_prefix: &RelPath,
|
||||
path_style: PathStyle,
|
||||
) -> (SharedString, Option<SharedString>) {
|
||||
if path == Path::new("") {
|
||||
(
|
||||
SharedString::from(
|
||||
path_prefix
|
||||
.trim_end_matches(std::path::MAIN_SEPARATOR)
|
||||
.to_string(),
|
||||
),
|
||||
None,
|
||||
)
|
||||
} else {
|
||||
let file_name = path
|
||||
.file_name()
|
||||
.unwrap_or_default()
|
||||
.to_string_lossy()
|
||||
.to_string()
|
||||
.into();
|
||||
|
||||
let mut directory = path_prefix
|
||||
.trim_end_matches(std::path::MAIN_SEPARATOR)
|
||||
.to_string();
|
||||
if !directory.ends_with('/') {
|
||||
directory.push('/');
|
||||
}
|
||||
if let Some(parent) = path.parent().filter(|parent| parent != &Path::new("")) {
|
||||
directory.push_str(&parent.to_string_lossy());
|
||||
directory.push('/');
|
||||
}
|
||||
|
||||
(file_name, Some(directory.into()))
|
||||
}
|
||||
let full_path = path_prefix.join(path);
|
||||
let file_name = full_path.file_name().unwrap_or_default();
|
||||
let display_path = full_path.display(path_style);
|
||||
let (directory, file_name) = display_path.split_at(display_path.len() - file_name.len());
|
||||
(
|
||||
file_name.to_string().into(),
|
||||
Some(SharedString::new(directory)).filter(|dir| !dir.is_empty()),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn render_file_context_entry(
|
||||
id: ElementId,
|
||||
worktree_id: WorktreeId,
|
||||
path: &Arc<Path>,
|
||||
path_prefix: &Arc<str>,
|
||||
path: &Arc<RelPath>,
|
||||
path_prefix: &Arc<RelPath>,
|
||||
is_directory: bool,
|
||||
path_style: PathStyle,
|
||||
context_store: WeakEntity<ContextStore>,
|
||||
cx: &App,
|
||||
) -> Stateful<Div> {
|
||||
let (file_name, directory) = extract_file_name_and_directory(path, path_prefix);
|
||||
let (file_name, directory) = extract_file_name_and_directory(path, path_prefix, path_style);
|
||||
|
||||
let added = context_store.upgrade().and_then(|context_store| {
|
||||
let project_path = ProjectPath {
|
||||
|
|
@ -330,9 +311,9 @@ pub fn render_file_context_entry(
|
|||
});
|
||||
|
||||
let file_icon = if is_directory {
|
||||
FileIcons::get_folder_icon(false, path, cx)
|
||||
FileIcons::get_folder_icon(false, path.as_std_path(), cx)
|
||||
} else {
|
||||
FileIcons::get_icon(path, cx)
|
||||
FileIcons::get_icon(path.as_std_path(), cx)
|
||||
}
|
||||
.map(Icon::from_path)
|
||||
.unwrap_or_else(|| Icon::new(IconName::File));
|
||||
|
|
|
|||
|
|
@ -2,13 +2,14 @@ use std::cmp::Reverse;
|
|||
use std::sync::Arc;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
|
||||
use anyhow::Result;
|
||||
use anyhow::{Result, anyhow};
|
||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||
use gpui::{
|
||||
App, AppContext, DismissEvent, Entity, FocusHandle, Focusable, Stateful, Task, WeakEntity,
|
||||
};
|
||||
use ordered_float::OrderedFloat;
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use project::lsp_store::SymbolLocation;
|
||||
use project::{DocumentSymbol, Symbol};
|
||||
use ui::{ListItem, prelude::*};
|
||||
use util::ResultExt as _;
|
||||
|
|
@ -191,7 +192,10 @@ pub(crate) fn add_symbol(
|
|||
) -> Task<Result<(Option<AgentContextHandle>, bool)>> {
|
||||
let project = workspace.read(cx).project().clone();
|
||||
let open_buffer_task = project.update(cx, |project, cx| {
|
||||
project.open_buffer(symbol.path.clone(), cx)
|
||||
let SymbolLocation::InProject(symbol_path) = &symbol.path else {
|
||||
return Task::ready(Err(anyhow!("can't add symbol from outside of project")));
|
||||
};
|
||||
project.open_buffer(symbol_path.clone(), cx)
|
||||
});
|
||||
cx.spawn(async move |cx| {
|
||||
let buffer = open_buffer_task.await?;
|
||||
|
|
@ -291,10 +295,11 @@ pub(crate) fn search_symbols(
|
|||
.map(|(id, symbol)| {
|
||||
StringMatchCandidate::new(id, symbol.label.filter_text())
|
||||
})
|
||||
.partition(|candidate| {
|
||||
project
|
||||
.entry_for_path(&symbols[candidate.id].path, cx)
|
||||
.is_some_and(|e| !e.is_ignored)
|
||||
.partition(|candidate| match &symbols[candidate.id].path {
|
||||
SymbolLocation::InProject(project_path) => project
|
||||
.entry_for_path(project_path, cx)
|
||||
.is_some_and(|e| !e.is_ignored),
|
||||
SymbolLocation::OutsideProject { .. } => false,
|
||||
})
|
||||
})
|
||||
.log_err()
|
||||
|
|
@ -360,13 +365,18 @@ fn compute_symbol_entries(
|
|||
}
|
||||
|
||||
pub fn render_symbol_context_entry(id: ElementId, entry: &SymbolEntry) -> Stateful<Div> {
|
||||
let path = entry
|
||||
.symbol
|
||||
.path
|
||||
.path
|
||||
.file_name()
|
||||
.map(|s| s.to_string_lossy())
|
||||
.unwrap_or_default();
|
||||
let path = match &entry.symbol.path {
|
||||
SymbolLocation::InProject(project_path) => {
|
||||
project_path.path.file_name().unwrap_or_default().into()
|
||||
}
|
||||
SymbolLocation::OutsideProject {
|
||||
abs_path,
|
||||
signature: _,
|
||||
} => abs_path
|
||||
.file_name()
|
||||
.map(|f| f.to_string_lossy())
|
||||
.unwrap_or_default(),
|
||||
};
|
||||
let symbol_location = format!("{} L{}", path, entry.symbol.range.start.0.row + 1);
|
||||
|
||||
h_flex()
|
||||
|
|
|
|||
|
|
@ -1431,10 +1431,14 @@ impl TextThreadEditor {
|
|||
else {
|
||||
continue;
|
||||
};
|
||||
let worktree_root_name = worktree.read(cx).root_name().to_string();
|
||||
let mut full_path = PathBuf::from(worktree_root_name.clone());
|
||||
full_path.push(&project_path.path);
|
||||
file_slash_command_args.push(full_path.to_string_lossy().to_string());
|
||||
let path_style = worktree.read(cx).path_style();
|
||||
let full_path = worktree
|
||||
.read(cx)
|
||||
.root_name()
|
||||
.join(&project_path.path)
|
||||
.display(path_style)
|
||||
.into_owned();
|
||||
file_slash_command_args.push(full_path);
|
||||
}
|
||||
|
||||
let cmd_name = FileSlashCommand.name();
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ parking_lot.workspace = true
|
|||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
ui.workspace = true
|
||||
util.workspace = true
|
||||
workspace.workspace = true
|
||||
workspace-hack.workspace = true
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, atomic::AtomicBool};
|
||||
|
||||
use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
use extension::{Extension, ExtensionHostProxy, ExtensionSlashCommandProxy, WorktreeDelegate};
|
||||
use gpui::{App, Task, WeakEntity, Window};
|
||||
use language::{BufferSnapshot, LspAdapterDelegate};
|
||||
use std::sync::{Arc, atomic::AtomicBool};
|
||||
use ui::prelude::*;
|
||||
use util::rel_path::RelPath;
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::{
|
||||
|
|
@ -54,7 +53,7 @@ impl WorktreeDelegate for WorktreeDelegateAdapter {
|
|||
self.0.worktree_root_path().to_string_lossy().to_string()
|
||||
}
|
||||
|
||||
async fn read_text_file(&self, path: PathBuf) -> Result<String> {
|
||||
async fn read_text_file(&self, path: &RelPath) -> Result<String> {
|
||||
self.0.read_text_file(path).await
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -41,6 +41,9 @@ worktree.workspace = true
|
|||
workspace-hack.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
fs = { workspace = true, features = ["test-support"] }
|
||||
gpui = { workspace = true, features = ["test-support"] }
|
||||
pretty_assertions.workspace = true
|
||||
settings.workspace = true
|
||||
project = { workspace = true, features = ["test-support"] }
|
||||
settings = { workspace = true, features = ["test-support"] }
|
||||
zlog.workspace = true
|
||||
|
|
|
|||
159
crates/assistant_slash_commands/src/cargo_workspace_command.rs
Normal file
159
crates/assistant_slash_commands/src/cargo_workspace_command.rs
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
use anyhow::{Context as _, Result, anyhow};
|
||||
use assistant_slash_command::{
|
||||
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
|
||||
SlashCommandResult,
|
||||
};
|
||||
use fs::Fs;
|
||||
use gpui::{App, Entity, Task, WeakEntity};
|
||||
use language::{BufferSnapshot, LspAdapterDelegate};
|
||||
use project::{Project, ProjectPath};
|
||||
use std::{
|
||||
fmt::Write,
|
||||
path::Path,
|
||||
sync::{Arc, atomic::AtomicBool},
|
||||
};
|
||||
use ui::prelude::*;
|
||||
use util::rel_path::RelPath;
|
||||
use workspace::Workspace;
|
||||
|
||||
pub struct CargoWorkspaceSlashCommand;
|
||||
|
||||
impl CargoWorkspaceSlashCommand {
|
||||
async fn build_message(fs: Arc<dyn Fs>, path_to_cargo_toml: &Path) -> Result<String> {
|
||||
let buffer = fs.load(path_to_cargo_toml).await?;
|
||||
let cargo_toml: cargo_toml::Manifest = toml::from_str(&buffer)?;
|
||||
|
||||
let mut message = String::new();
|
||||
writeln!(message, "You are in a Rust project.")?;
|
||||
|
||||
if let Some(workspace) = cargo_toml.workspace {
|
||||
writeln!(
|
||||
message,
|
||||
"The project is a Cargo workspace with the following members:"
|
||||
)?;
|
||||
for member in workspace.members {
|
||||
writeln!(message, "- {member}")?;
|
||||
}
|
||||
|
||||
if !workspace.default_members.is_empty() {
|
||||
writeln!(message, "The default members are:")?;
|
||||
for member in workspace.default_members {
|
||||
writeln!(message, "- {member}")?;
|
||||
}
|
||||
}
|
||||
|
||||
if !workspace.dependencies.is_empty() {
|
||||
writeln!(
|
||||
message,
|
||||
"The following workspace dependencies are installed:"
|
||||
)?;
|
||||
for dependency in workspace.dependencies.keys() {
|
||||
writeln!(message, "- {dependency}")?;
|
||||
}
|
||||
}
|
||||
} else if let Some(package) = cargo_toml.package {
|
||||
writeln!(
|
||||
message,
|
||||
"The project name is \"{name}\".",
|
||||
name = package.name
|
||||
)?;
|
||||
|
||||
let description = package
|
||||
.description
|
||||
.as_ref()
|
||||
.and_then(|description| description.get().ok().cloned());
|
||||
if let Some(description) = description.as_ref() {
|
||||
writeln!(message, "It describes itself as \"{description}\".")?;
|
||||
}
|
||||
|
||||
if !cargo_toml.dependencies.is_empty() {
|
||||
writeln!(message, "The following dependencies are installed:")?;
|
||||
for dependency in cargo_toml.dependencies.keys() {
|
||||
writeln!(message, "- {dependency}")?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(message)
|
||||
}
|
||||
|
||||
fn path_to_cargo_toml(project: Entity<Project>, cx: &mut App) -> Option<Arc<Path>> {
|
||||
let worktree = project.read(cx).worktrees(cx).next()?;
|
||||
let worktree = worktree.read(cx);
|
||||
let entry = worktree.entry_for_path(RelPath::new("Cargo.toml").unwrap())?;
|
||||
let path = ProjectPath {
|
||||
worktree_id: worktree.id(),
|
||||
path: entry.path.clone(),
|
||||
};
|
||||
Some(Arc::from(
|
||||
project.read(cx).absolute_path(&path, cx)?.as_path(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl SlashCommand for CargoWorkspaceSlashCommand {
|
||||
fn name(&self) -> String {
|
||||
"cargo-workspace".into()
|
||||
}
|
||||
|
||||
fn description(&self) -> String {
|
||||
"insert project workspace metadata".into()
|
||||
}
|
||||
|
||||
fn menu_text(&self) -> String {
|
||||
"Insert Project Workspace Metadata".into()
|
||||
}
|
||||
|
||||
fn complete_argument(
|
||||
self: Arc<Self>,
|
||||
_arguments: &[String],
|
||||
_cancel: Arc<AtomicBool>,
|
||||
_workspace: Option<WeakEntity<Workspace>>,
|
||||
_window: &mut Window,
|
||||
_cx: &mut App,
|
||||
) -> Task<Result<Vec<ArgumentCompletion>>> {
|
||||
Task::ready(Err(anyhow!("this command does not require argument")))
|
||||
}
|
||||
|
||||
fn requires_argument(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn run(
|
||||
self: Arc<Self>,
|
||||
_arguments: &[String],
|
||||
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||
_context_buffer: BufferSnapshot,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||
_window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Task<SlashCommandResult> {
|
||||
let output = workspace.update(cx, |workspace, cx| {
|
||||
let project = workspace.project().clone();
|
||||
let fs = workspace.project().read(cx).fs().clone();
|
||||
let path = Self::path_to_cargo_toml(project, cx);
|
||||
let output = cx.background_spawn(async move {
|
||||
let path = path.with_context(|| "Cargo.toml not found")?;
|
||||
Self::build_message(fs, &path).await
|
||||
});
|
||||
|
||||
cx.foreground_executor().spawn(async move {
|
||||
let text = output.await?;
|
||||
let range = 0..text.len();
|
||||
Ok(SlashCommandOutput {
|
||||
text,
|
||||
sections: vec![SlashCommandOutputSection {
|
||||
range,
|
||||
icon: IconName::FileTree,
|
||||
label: "Project".into(),
|
||||
metadata: None,
|
||||
}],
|
||||
run_commands_in_text: false,
|
||||
}
|
||||
.into_event_stream())
|
||||
})
|
||||
});
|
||||
output.unwrap_or_else(|error| Task::ready(Err(error)))
|
||||
}
|
||||
}
|
||||
|
|
@ -13,12 +13,12 @@ use project::{DiagnosticSummary, PathMatchCandidateSet, Project};
|
|||
use rope::Point;
|
||||
use std::{
|
||||
fmt::Write,
|
||||
path::{Path, PathBuf},
|
||||
path::Path,
|
||||
sync::{Arc, atomic::AtomicBool},
|
||||
};
|
||||
use ui::prelude::*;
|
||||
use util::ResultExt;
|
||||
use util::paths::PathMatcher;
|
||||
use util::paths::{PathMatcher, PathStyle};
|
||||
use util::{ResultExt, rel_path::RelPath};
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::create_label_for_command;
|
||||
|
|
@ -36,7 +36,7 @@ impl DiagnosticsSlashCommand {
|
|||
if query.is_empty() {
|
||||
let workspace = workspace.read(cx);
|
||||
let entries = workspace.recent_navigation_history(Some(10), cx);
|
||||
let path_prefix: Arc<str> = Arc::default();
|
||||
let path_prefix: Arc<RelPath> = RelPath::empty().into();
|
||||
Task::ready(
|
||||
entries
|
||||
.into_iter()
|
||||
|
|
@ -125,6 +125,7 @@ impl SlashCommand for DiagnosticsSlashCommand {
|
|||
let Some(workspace) = workspace.and_then(|workspace| workspace.upgrade()) else {
|
||||
return Task::ready(Err(anyhow!("workspace was dropped")));
|
||||
};
|
||||
let path_style = workspace.read(cx).project().read(cx).path_style(cx);
|
||||
let query = arguments.last().cloned().unwrap_or_default();
|
||||
|
||||
let paths = self.search_paths(query.clone(), cancellation_flag.clone(), &workspace, cx);
|
||||
|
|
@ -134,11 +135,11 @@ impl SlashCommand for DiagnosticsSlashCommand {
|
|||
.await
|
||||
.into_iter()
|
||||
.map(|path_match| {
|
||||
format!(
|
||||
"{}{}",
|
||||
path_match.path_prefix,
|
||||
path_match.path.to_string_lossy()
|
||||
)
|
||||
path_match
|
||||
.path_prefix
|
||||
.join(&path_match.path)
|
||||
.display(path_style)
|
||||
.to_string()
|
||||
})
|
||||
.collect();
|
||||
|
||||
|
|
@ -183,9 +184,11 @@ impl SlashCommand for DiagnosticsSlashCommand {
|
|||
return Task::ready(Err(anyhow!("workspace was dropped")));
|
||||
};
|
||||
|
||||
let options = Options::parse(arguments);
|
||||
let project = workspace.read(cx).project();
|
||||
let path_style = project.read(cx).path_style(cx);
|
||||
let options = Options::parse(arguments, path_style);
|
||||
|
||||
let task = collect_diagnostics(workspace.read(cx).project().clone(), options, cx);
|
||||
let task = collect_diagnostics(project.clone(), options, cx);
|
||||
|
||||
window.spawn(cx, async move |_| {
|
||||
task.await?
|
||||
|
|
@ -204,14 +207,14 @@ struct Options {
|
|||
const INCLUDE_WARNINGS_ARGUMENT: &str = "--include-warnings";
|
||||
|
||||
impl Options {
|
||||
fn parse(arguments: &[String]) -> Self {
|
||||
fn parse(arguments: &[String], path_style: PathStyle) -> Self {
|
||||
let mut include_warnings = false;
|
||||
let mut path_matcher = None;
|
||||
for arg in arguments {
|
||||
if arg == INCLUDE_WARNINGS_ARGUMENT {
|
||||
include_warnings = true;
|
||||
} else {
|
||||
path_matcher = PathMatcher::new(&[arg.to_owned()]).log_err();
|
||||
path_matcher = PathMatcher::new(&[arg.to_owned()], path_style).log_err();
|
||||
}
|
||||
}
|
||||
Self {
|
||||
|
|
@ -237,21 +240,15 @@ fn collect_diagnostics(
|
|||
None
|
||||
};
|
||||
|
||||
let path_style = project.read(cx).path_style(cx);
|
||||
let glob_is_exact_file_match = if let Some(path) = options
|
||||
.path_matcher
|
||||
.as_ref()
|
||||
.and_then(|pm| pm.sources().first())
|
||||
{
|
||||
PathBuf::try_from(path)
|
||||
.ok()
|
||||
.and_then(|path| {
|
||||
project.read(cx).worktrees(cx).find_map(|worktree| {
|
||||
let worktree = worktree.read(cx);
|
||||
let worktree_root_path = Path::new(worktree.root_name());
|
||||
let relative_path = path.strip_prefix(worktree_root_path).ok()?;
|
||||
worktree.absolutize(relative_path).ok()
|
||||
})
|
||||
})
|
||||
project
|
||||
.read(cx)
|
||||
.find_project_path(Path::new(path), cx)
|
||||
.is_some()
|
||||
} else {
|
||||
false
|
||||
|
|
@ -263,9 +260,8 @@ fn collect_diagnostics(
|
|||
.diagnostic_summaries(false, cx)
|
||||
.flat_map(|(path, _, summary)| {
|
||||
let worktree = project.read(cx).worktree_for_id(path.worktree_id, cx)?;
|
||||
let mut path_buf = PathBuf::from(worktree.read(cx).root_name());
|
||||
path_buf.push(&path.path);
|
||||
Some((path, path_buf, summary))
|
||||
let full_path = worktree.read(cx).root_name().join(&path.path);
|
||||
Some((path, full_path, summary))
|
||||
})
|
||||
.collect();
|
||||
|
||||
|
|
@ -281,7 +277,7 @@ fn collect_diagnostics(
|
|||
let mut project_summary = DiagnosticSummary::default();
|
||||
for (project_path, path, summary) in diagnostic_summaries {
|
||||
if let Some(path_matcher) = &options.path_matcher
|
||||
&& !path_matcher.is_match(&path)
|
||||
&& !path_matcher.is_match(&path.as_std_path())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
|
@ -294,7 +290,7 @@ fn collect_diagnostics(
|
|||
}
|
||||
|
||||
let last_end = output.text.len();
|
||||
let file_path = path.to_string_lossy().to_string();
|
||||
let file_path = path.display(path_style).to_string();
|
||||
if !glob_is_exact_file_match {
|
||||
writeln!(&mut output.text, "{file_path}").unwrap();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,11 +14,11 @@ use smol::stream::StreamExt;
|
|||
use std::{
|
||||
fmt::Write,
|
||||
ops::{Range, RangeInclusive},
|
||||
path::{Path, PathBuf},
|
||||
path::Path,
|
||||
sync::{Arc, atomic::AtomicBool},
|
||||
};
|
||||
use ui::prelude::*;
|
||||
use util::ResultExt;
|
||||
use util::{ResultExt, rel_path::RelPath};
|
||||
use workspace::Workspace;
|
||||
use worktree::ChildEntriesOptions;
|
||||
|
||||
|
|
@ -48,7 +48,7 @@ impl FileSlashCommand {
|
|||
include_dirs: true,
|
||||
include_ignored: false,
|
||||
};
|
||||
let entries = worktree.child_entries_with_options(Path::new(""), options);
|
||||
let entries = worktree.child_entries_with_options(RelPath::empty(), options);
|
||||
entries.map(move |entry| {
|
||||
(
|
||||
project::ProjectPath {
|
||||
|
|
@ -61,19 +61,18 @@ impl FileSlashCommand {
|
|||
}))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let path_prefix: Arc<str> = Arc::default();
|
||||
let path_prefix: Arc<RelPath> = RelPath::empty().into();
|
||||
Task::ready(
|
||||
entries
|
||||
.into_iter()
|
||||
.filter_map(|(entry, is_dir)| {
|
||||
let worktree = project.worktree_for_id(entry.worktree_id, cx)?;
|
||||
let mut full_path = PathBuf::from(worktree.read(cx).root_name());
|
||||
full_path.push(&entry.path);
|
||||
let full_path = worktree.read(cx).root_name().join(&entry.path);
|
||||
Some(PathMatch {
|
||||
score: 0.,
|
||||
positions: Vec::new(),
|
||||
worktree_id: entry.worktree_id.to_usize(),
|
||||
path: full_path.into(),
|
||||
path: full_path,
|
||||
path_prefix: path_prefix.clone(),
|
||||
distance_to_relative_ancestor: 0,
|
||||
is_dir,
|
||||
|
|
@ -149,6 +148,8 @@ impl SlashCommand for FileSlashCommand {
|
|||
return Task::ready(Err(anyhow!("workspace was dropped")));
|
||||
};
|
||||
|
||||
let path_style = workspace.read(cx).path_style(cx);
|
||||
|
||||
let paths = self.search_paths(
|
||||
arguments.last().cloned().unwrap_or_default(),
|
||||
cancellation_flag,
|
||||
|
|
@ -161,14 +162,14 @@ impl SlashCommand for FileSlashCommand {
|
|||
.await
|
||||
.into_iter()
|
||||
.filter_map(|path_match| {
|
||||
let text = format!(
|
||||
"{}{}",
|
||||
path_match.path_prefix,
|
||||
path_match.path.to_string_lossy()
|
||||
);
|
||||
let text = path_match
|
||||
.path_prefix
|
||||
.join(&path_match.path)
|
||||
.display(path_style)
|
||||
.to_string();
|
||||
|
||||
let mut label = CodeLabel::default();
|
||||
let file_name = path_match.path.file_name()?.to_string_lossy();
|
||||
let file_name = path_match.path.file_name()?;
|
||||
let label_text = if path_match.is_dir {
|
||||
format!("{}/ ", file_name)
|
||||
} else {
|
||||
|
|
@ -247,14 +248,13 @@ fn collect_files(
|
|||
cx.spawn(async move |cx| {
|
||||
for snapshot in snapshots {
|
||||
let worktree_id = snapshot.id();
|
||||
let mut directory_stack: Vec<Arc<Path>> = Vec::new();
|
||||
let mut folded_directory_names_stack = Vec::new();
|
||||
let path_style = snapshot.path_style();
|
||||
let mut directory_stack: Vec<Arc<RelPath>> = Vec::new();
|
||||
let mut folded_directory_names: Arc<RelPath> = RelPath::empty().into();
|
||||
let mut is_top_level_directory = true;
|
||||
|
||||
for entry in snapshot.entries(false, 0) {
|
||||
let mut path_including_worktree_name = PathBuf::new();
|
||||
path_including_worktree_name.push(snapshot.root_name());
|
||||
path_including_worktree_name.push(&entry.path);
|
||||
let path_including_worktree_name = snapshot.root_name().join(&entry.path);
|
||||
|
||||
if !matchers
|
||||
.iter()
|
||||
|
|
@ -277,13 +277,7 @@ fn collect_files(
|
|||
)))?;
|
||||
}
|
||||
|
||||
let filename = entry
|
||||
.path
|
||||
.file_name()
|
||||
.unwrap_or_default()
|
||||
.to_str()
|
||||
.unwrap_or_default()
|
||||
.to_string();
|
||||
let filename = entry.path.file_name().unwrap_or_default().to_string();
|
||||
|
||||
if entry.is_dir() {
|
||||
// Auto-fold directories that contain no files
|
||||
|
|
@ -292,24 +286,23 @@ fn collect_files(
|
|||
if child_entries.next().is_none() && child.kind.is_dir() {
|
||||
if is_top_level_directory {
|
||||
is_top_level_directory = false;
|
||||
folded_directory_names_stack.push(
|
||||
path_including_worktree_name.to_string_lossy().to_string(),
|
||||
);
|
||||
folded_directory_names =
|
||||
folded_directory_names.join(&path_including_worktree_name);
|
||||
} else {
|
||||
folded_directory_names_stack.push(filename.to_string());
|
||||
folded_directory_names =
|
||||
folded_directory_names.join(RelPath::new(&filename).unwrap());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
// Skip empty directories
|
||||
folded_directory_names_stack.clear();
|
||||
folded_directory_names = RelPath::empty().into();
|
||||
continue;
|
||||
}
|
||||
let prefix_paths = folded_directory_names_stack.drain(..).as_slice().join("/");
|
||||
if prefix_paths.is_empty() {
|
||||
if folded_directory_names.is_empty() {
|
||||
let label = if is_top_level_directory {
|
||||
is_top_level_directory = false;
|
||||
path_including_worktree_name.to_string_lossy().to_string()
|
||||
path_including_worktree_name.display(path_style).to_string()
|
||||
} else {
|
||||
filename
|
||||
};
|
||||
|
|
@ -320,28 +313,23 @@ fn collect_files(
|
|||
}))?;
|
||||
events_tx.unbounded_send(Ok(SlashCommandEvent::Content(
|
||||
SlashCommandContent::Text {
|
||||
text: label,
|
||||
text: label.to_string(),
|
||||
run_commands_in_text: false,
|
||||
},
|
||||
)))?;
|
||||
directory_stack.push(entry.path.clone());
|
||||
} else {
|
||||
// todo(windows)
|
||||
// Potential bug: this assumes that the path separator is always `\` on Windows
|
||||
let entry_name = format!(
|
||||
"{}{}{}",
|
||||
prefix_paths,
|
||||
std::path::MAIN_SEPARATOR_STR,
|
||||
&filename
|
||||
);
|
||||
let entry_name =
|
||||
folded_directory_names.join(RelPath::new(&filename).unwrap());
|
||||
let entry_name = entry_name.display(path_style);
|
||||
events_tx.unbounded_send(Ok(SlashCommandEvent::StartSection {
|
||||
icon: IconName::Folder,
|
||||
label: entry_name.clone().into(),
|
||||
label: entry_name.to_string().into(),
|
||||
metadata: None,
|
||||
}))?;
|
||||
events_tx.unbounded_send(Ok(SlashCommandEvent::Content(
|
||||
SlashCommandContent::Text {
|
||||
text: entry_name,
|
||||
text: entry_name.to_string(),
|
||||
run_commands_in_text: false,
|
||||
},
|
||||
)))?;
|
||||
|
|
@ -356,7 +344,7 @@ fn collect_files(
|
|||
} else if entry.is_file() {
|
||||
let Some(open_buffer_task) = project_handle
|
||||
.update(cx, |project, cx| {
|
||||
project.open_buffer((worktree_id, &entry.path), cx)
|
||||
project.open_buffer((worktree_id, entry.path.clone()), cx)
|
||||
})
|
||||
.ok()
|
||||
else {
|
||||
|
|
@ -367,7 +355,9 @@ fn collect_files(
|
|||
let snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot())?;
|
||||
append_buffer_to_output(
|
||||
&snapshot,
|
||||
Some(&path_including_worktree_name),
|
||||
Some(Path::new(
|
||||
path_including_worktree_name.display(path_style).as_ref(),
|
||||
)),
|
||||
&mut output,
|
||||
)
|
||||
.log_err();
|
||||
|
|
@ -462,10 +452,9 @@ pub fn build_entry_output_section(
|
|||
/// This contains a small fork of the util::paths::PathMatcher, that is stricter about the prefix
|
||||
/// check. Only subpaths pass the prefix check, rather than any prefix.
|
||||
mod custom_path_matcher {
|
||||
use std::{fmt::Debug as _, path::Path};
|
||||
|
||||
use globset::{Glob, GlobSet, GlobSetBuilder};
|
||||
use util::paths::SanitizedPath;
|
||||
use std::fmt::Debug as _;
|
||||
use util::{paths::SanitizedPath, rel_path::RelPath};
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct PathMatcher {
|
||||
|
|
@ -492,12 +481,12 @@ mod custom_path_matcher {
|
|||
pub fn new(globs: &[String]) -> Result<Self, globset::Error> {
|
||||
let globs = globs
|
||||
.iter()
|
||||
.map(|glob| Glob::new(&SanitizedPath::new(glob).to_glob_string()))
|
||||
.map(|glob| Glob::new(&SanitizedPath::new(glob).to_string()))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
let sources = globs.iter().map(|glob| glob.glob().to_owned()).collect();
|
||||
let sources_with_trailing_slash = globs
|
||||
.iter()
|
||||
.map(|glob| glob.glob().to_string() + std::path::MAIN_SEPARATOR_STR)
|
||||
.map(|glob| glob.glob().to_string() + "/")
|
||||
.collect();
|
||||
let mut glob_builder = GlobSetBuilder::new();
|
||||
for single_glob in globs {
|
||||
|
|
@ -511,16 +500,13 @@ mod custom_path_matcher {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn is_match<P: AsRef<Path>>(&self, other: P) -> bool {
|
||||
let other_path = other.as_ref();
|
||||
pub fn is_match(&self, other: &RelPath) -> bool {
|
||||
self.sources
|
||||
.iter()
|
||||
.zip(self.sources_with_trailing_slash.iter())
|
||||
.any(|(source, with_slash)| {
|
||||
let as_bytes = other_path.as_os_str().as_encoded_bytes();
|
||||
// todo(windows)
|
||||
// Potential bug: this assumes that the path separator is always `\` on Windows
|
||||
let with_slash = if source.ends_with(std::path::MAIN_SEPARATOR_STR) {
|
||||
let as_bytes = other.as_str().as_bytes();
|
||||
let with_slash = if source.ends_with('/') {
|
||||
source.as_bytes()
|
||||
} else {
|
||||
with_slash.as_bytes()
|
||||
|
|
@ -528,13 +514,13 @@ mod custom_path_matcher {
|
|||
|
||||
as_bytes.starts_with(with_slash) || as_bytes.ends_with(source.as_bytes())
|
||||
})
|
||||
|| self.glob.is_match(other_path)
|
||||
|| self.check_with_end_separator(other_path)
|
||||
|| self.glob.is_match(other)
|
||||
|| self.check_with_end_separator(other)
|
||||
}
|
||||
|
||||
fn check_with_end_separator(&self, path: &Path) -> bool {
|
||||
let path_str = path.to_string_lossy();
|
||||
let separator = std::path::MAIN_SEPARATOR_STR;
|
||||
fn check_with_end_separator(&self, path: &RelPath) -> bool {
|
||||
let path_str = path.as_str();
|
||||
let separator = "/";
|
||||
if path_str.ends_with(separator) {
|
||||
false
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -96,9 +96,7 @@ impl Tool for CopyPathTool {
|
|||
.and_then(|project_path| project.entry_for_path(&project_path, cx))
|
||||
{
|
||||
Some(entity) => match project.find_project_path(&input.destination_path, cx) {
|
||||
Some(project_path) => {
|
||||
project.copy_entry(entity.id, None, project_path.path, cx)
|
||||
}
|
||||
Some(project_path) => project.copy_entry(entity.id, project_path, cx),
|
||||
None => Task::ready(Err(anyhow!(
|
||||
"Destination path {} was outside the project.",
|
||||
input.destination_path
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use language_model::{LanguageModel, LanguageModelRequest, LanguageModelToolSchem
|
|||
use project::Project;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{fmt::Write, path::Path, sync::Arc};
|
||||
use std::{fmt::Write, sync::Arc};
|
||||
use ui::IconName;
|
||||
use util::markdown::MarkdownInlineCode;
|
||||
|
||||
|
|
@ -150,9 +150,7 @@ impl Tool for DiagnosticsTool {
|
|||
has_diagnostics = true;
|
||||
output.push_str(&format!(
|
||||
"{}: {} error(s), {} warning(s)\n",
|
||||
Path::new(worktree.read(cx).root_name())
|
||||
.join(project_path.path)
|
||||
.display(),
|
||||
worktree.read(cx).absolutize(&project_path.path).display(),
|
||||
summary.error_count,
|
||||
summary.warning_count
|
||||
));
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ use settings::Settings;
|
|||
use std::{
|
||||
cmp::Reverse,
|
||||
collections::HashSet,
|
||||
ffi::OsStr,
|
||||
ops::Range,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
|
|
@ -45,7 +46,7 @@ use std::{
|
|||
};
|
||||
use theme::ThemeSettings;
|
||||
use ui::{CommonAnimationExt, Disclosure, Tooltip, prelude::*};
|
||||
use util::ResultExt;
|
||||
use util::{ResultExt, rel_path::RelPath};
|
||||
use workspace::Workspace;
|
||||
|
||||
pub struct EditFileTool;
|
||||
|
|
@ -146,11 +147,11 @@ impl Tool for EditFileTool {
|
|||
|
||||
// If any path component matches the local settings folder, then this could affect
|
||||
// the editor in ways beyond the project source, so prompt.
|
||||
let local_settings_folder = paths::local_settings_folder_relative_path();
|
||||
let local_settings_folder = paths::local_settings_folder_name();
|
||||
let path = Path::new(&input.path);
|
||||
if path
|
||||
.components()
|
||||
.any(|component| component.as_os_str() == local_settings_folder.as_os_str())
|
||||
.any(|c| c.as_os_str() == <str as AsRef<OsStr>>::as_ref(local_settings_folder))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
|
@ -195,10 +196,10 @@ impl Tool for EditFileTool {
|
|||
let mut description = input.display_description.clone();
|
||||
|
||||
// Add context about why confirmation may be needed
|
||||
let local_settings_folder = paths::local_settings_folder_relative_path();
|
||||
let local_settings_folder = paths::local_settings_folder_name();
|
||||
if path
|
||||
.components()
|
||||
.any(|c| c.as_os_str() == local_settings_folder.as_os_str())
|
||||
.any(|c| c.as_os_str() == <str as AsRef<OsStr>>::as_ref(local_settings_folder))
|
||||
{
|
||||
description.push_str(" (local settings)");
|
||||
} else if let Ok(canonical_path) = std::fs::canonicalize(&input.path)
|
||||
|
|
@ -377,7 +378,7 @@ impl Tool for EditFileTool {
|
|||
.await;
|
||||
|
||||
let output = EditFileToolOutput {
|
||||
original_path: project_path.path.to_path_buf(),
|
||||
original_path: project_path.path.as_std_path().to_owned(),
|
||||
new_text,
|
||||
old_text,
|
||||
raw_output: Some(agent_output),
|
||||
|
|
@ -549,10 +550,11 @@ fn resolve_path(
|
|||
let file_name = input
|
||||
.path
|
||||
.file_name()
|
||||
.and_then(|file_name| file_name.to_str())
|
||||
.context("Can't create file: invalid filename")?;
|
||||
|
||||
let new_file_path = parent_project_path.map(|parent| ProjectPath {
|
||||
path: Arc::from(parent.path.join(file_name)),
|
||||
path: parent.path.join(RelPath::new(file_name).unwrap()),
|
||||
..parent
|
||||
});
|
||||
|
||||
|
|
@ -1236,7 +1238,7 @@ mod tests {
|
|||
use serde_json::json;
|
||||
use settings::SettingsStore;
|
||||
use std::fs;
|
||||
use util::path;
|
||||
use util::{path, rel_path::rel_path};
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_edit_nonexistent_file(cx: &mut TestAppContext) {
|
||||
|
|
@ -1355,14 +1357,10 @@ mod tests {
|
|||
cx.update(|cx| resolve_path(&input, project, cx))
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn assert_resolved_path_eq(path: anyhow::Result<ProjectPath>, expected: &str) {
|
||||
let actual = path
|
||||
.expect("Should return valid path")
|
||||
.path
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.replace("\\", "/"); // Naive Windows paths normalization
|
||||
assert_eq!(actual, expected);
|
||||
let actual = path.expect("Should return valid path").path;
|
||||
assert_eq!(actual.as_ref(), rel_path(expected));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -1976,25 +1974,22 @@ mod tests {
|
|||
let project = Project::test(fs.clone(), [path!("/home/user/myproject").as_ref()], cx).await;
|
||||
|
||||
// Get the actual local settings folder name
|
||||
let local_settings_folder = paths::local_settings_folder_relative_path();
|
||||
let local_settings_folder = paths::local_settings_folder_name();
|
||||
|
||||
// Test various config path patterns
|
||||
let test_cases = vec![
|
||||
(
|
||||
format!("{}/settings.json", local_settings_folder.display()),
|
||||
format!("{local_settings_folder}/settings.json"),
|
||||
true,
|
||||
"Top-level local settings file".to_string(),
|
||||
),
|
||||
(
|
||||
format!(
|
||||
"myproject/{}/settings.json",
|
||||
local_settings_folder.display()
|
||||
),
|
||||
format!("myproject/{local_settings_folder}/settings.json"),
|
||||
true,
|
||||
"Local settings in project path".to_string(),
|
||||
),
|
||||
(
|
||||
format!("src/{}/config.toml", local_settings_folder.display()),
|
||||
format!("src/{local_settings_folder}/config.toml"),
|
||||
true,
|
||||
"Local settings in subdirectory".to_string(),
|
||||
),
|
||||
|
|
@ -2205,12 +2200,7 @@ mod tests {
|
|||
("", false, "Empty path is treated as project root"),
|
||||
// Root directory
|
||||
("/", true, "Root directory should be outside project"),
|
||||
// Parent directory references - find_project_path resolves these
|
||||
(
|
||||
"project/../other",
|
||||
false,
|
||||
"Path with .. is resolved by find_project_path",
|
||||
),
|
||||
("project/../other", true, "Path with .. is outside project"),
|
||||
(
|
||||
"project/./src/file.rs",
|
||||
false,
|
||||
|
|
|
|||
|
|
@ -161,10 +161,13 @@ impl Tool for FindPathTool {
|
|||
}
|
||||
|
||||
fn search_paths(glob: &str, project: Entity<Project>, cx: &mut App) -> Task<Result<Vec<PathBuf>>> {
|
||||
let path_matcher = match PathMatcher::new([
|
||||
// Sometimes models try to search for "". In this case, return all paths in the project.
|
||||
if glob.is_empty() { "*" } else { glob },
|
||||
]) {
|
||||
let path_matcher = match PathMatcher::new(
|
||||
[
|
||||
// Sometimes models try to search for "". In this case, return all paths in the project.
|
||||
if glob.is_empty() { "*" } else { glob },
|
||||
],
|
||||
project.read(cx).path_style(cx),
|
||||
) {
|
||||
Ok(matcher) => matcher,
|
||||
Err(err) => return Task::ready(Err(anyhow!("Invalid glob: {err}"))),
|
||||
};
|
||||
|
|
@ -178,10 +181,15 @@ fn search_paths(glob: &str, project: Entity<Project>, cx: &mut App) -> Task<Resu
|
|||
Ok(snapshots
|
||||
.iter()
|
||||
.flat_map(|snapshot| {
|
||||
let root_name = PathBuf::from(snapshot.root_name());
|
||||
snapshot
|
||||
.entries(false, 0)
|
||||
.map(move |entry| root_name.join(&entry.path))
|
||||
.map(move |entry| {
|
||||
snapshot
|
||||
.root_name()
|
||||
.join(&entry.path)
|
||||
.as_std_path()
|
||||
.to_path_buf()
|
||||
})
|
||||
.filter(|path| path_matcher.is_match(&path))
|
||||
})
|
||||
.collect())
|
||||
|
|
|
|||
|
|
@ -125,6 +125,7 @@ impl Tool for GrepTool {
|
|||
.as_ref()
|
||||
.into_iter()
|
||||
.collect::<Vec<_>>(),
|
||||
project.read(cx).path_style(cx),
|
||||
) {
|
||||
Ok(matcher) => matcher,
|
||||
Err(error) => {
|
||||
|
|
@ -141,7 +142,7 @@ impl Tool for GrepTool {
|
|||
.iter()
|
||||
.chain(global_settings.private_files.sources().iter());
|
||||
|
||||
match PathMatcher::new(exclude_patterns) {
|
||||
match PathMatcher::new(exclude_patterns, project.read(cx).path_style(cx)) {
|
||||
Ok(matcher) => matcher,
|
||||
Err(error) => {
|
||||
return Task::ready(Err(anyhow!("invalid exclude pattern: {error}"))).into();
|
||||
|
|
|
|||
|
|
@ -4,11 +4,11 @@ use anyhow::{Result, anyhow};
|
|||
use assistant_tool::{Tool, ToolResult};
|
||||
use gpui::{AnyWindowHandle, App, Entity, Task};
|
||||
use language_model::{LanguageModel, LanguageModelRequest, LanguageModelToolSchemaFormat};
|
||||
use project::{Project, WorktreeSettings};
|
||||
use project::{Project, ProjectPath, WorktreeSettings};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::Settings;
|
||||
use std::{fmt::Write, path::Path, sync::Arc};
|
||||
use std::{fmt::Write, sync::Arc};
|
||||
use ui::IconName;
|
||||
use util::markdown::MarkdownInlineCode;
|
||||
|
||||
|
|
@ -100,7 +100,7 @@ impl Tool for ListDirectoryTool {
|
|||
.filter_map(|worktree| {
|
||||
worktree.read(cx).root_entry().and_then(|entry| {
|
||||
if entry.is_dir() {
|
||||
entry.path.to_str()
|
||||
Some(entry.path.as_str())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
|
@ -158,7 +158,6 @@ impl Tool for ListDirectoryTool {
|
|||
}
|
||||
|
||||
let worktree_snapshot = worktree.read(cx).snapshot();
|
||||
let worktree_root_name = worktree.read(cx).root_name().to_string();
|
||||
|
||||
let Some(entry) = worktree_snapshot.entry_for_path(&project_path.path) else {
|
||||
return Task::ready(Err(anyhow!("Path not found: {}", input.path))).into();
|
||||
|
|
@ -180,23 +179,22 @@ impl Tool for ListDirectoryTool {
|
|||
continue;
|
||||
}
|
||||
|
||||
if project
|
||||
.read(cx)
|
||||
.find_project_path(&entry.path, cx)
|
||||
.map(|project_path| {
|
||||
let worktree_settings = WorktreeSettings::get(Some((&project_path).into()), cx);
|
||||
let project_path = ProjectPath {
|
||||
worktree_id: worktree_snapshot.id(),
|
||||
path: entry.path.clone(),
|
||||
};
|
||||
let worktree_settings = WorktreeSettings::get(Some((&project_path).into()), cx);
|
||||
|
||||
worktree_settings.is_path_excluded(&project_path.path)
|
||||
|| worktree_settings.is_path_private(&project_path.path)
|
||||
})
|
||||
.unwrap_or(false)
|
||||
if worktree_settings.is_path_excluded(&project_path.path)
|
||||
|| worktree_settings.is_path_private(&project_path.path)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let full_path = Path::new(&worktree_root_name)
|
||||
let full_path = worktree_snapshot
|
||||
.root_name()
|
||||
.join(&entry.path)
|
||||
.display()
|
||||
.display(worktree_snapshot.path_style())
|
||||
.to_string();
|
||||
if entry.is_dir() {
|
||||
folders.push(full_path);
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ impl Tool for MovePathTool {
|
|||
.and_then(|project_path| project.entry_for_path(&project_path, cx))
|
||||
{
|
||||
Some(entity) => match project.find_project_path(&input.destination_path, cx) {
|
||||
Some(project_path) => project.rename_entry(entity.id, project_path.path, cx),
|
||||
Some(project_path) => project.rename_entry(entity.id, project_path, cx),
|
||||
None => Task::ready(Err(anyhow!(
|
||||
"Destination path {} was outside the project.",
|
||||
input.destination_path
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ use postage::{sink::Sink, stream::Stream, watch};
|
|||
use project::Project;
|
||||
use settings::Settings as _;
|
||||
use std::{future::Future, mem, rc::Rc, sync::Arc, time::Duration};
|
||||
use util::{ResultExt, TryFutureExt, post_inc};
|
||||
use util::{ResultExt, TryFutureExt, paths::PathStyle, post_inc};
|
||||
|
||||
pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30);
|
||||
|
||||
|
|
@ -1163,6 +1163,7 @@ impl Room {
|
|||
room_id: self.id(),
|
||||
worktrees: project.read(cx).worktree_metadata_protos(cx),
|
||||
is_ssh_project: project.read(cx).is_via_remote_server(),
|
||||
windows_paths: Some(project.read(cx).path_style(cx) == PathStyle::Windows),
|
||||
});
|
||||
|
||||
cx.spawn(async move |this, cx| {
|
||||
|
|
|
|||
|
|
@ -405,7 +405,7 @@ impl Telemetry {
|
|||
let mut project_types: HashSet<&str> = HashSet::new();
|
||||
|
||||
for (path, _, _) in updated_entries_set.iter() {
|
||||
let Some(file_name) = path.file_name().and_then(|f| f.to_str()) else {
|
||||
let Some(file_name) = path.file_name() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
|
|
@ -601,6 +601,7 @@ mod tests {
|
|||
use http_client::FakeHttpClient;
|
||||
use std::collections::HashMap;
|
||||
use telemetry_events::FlexibleEvent;
|
||||
use util::rel_path::RelPath;
|
||||
use worktree::{PathChange, ProjectEntryId, WorktreeId};
|
||||
|
||||
#[gpui::test]
|
||||
|
|
@ -855,12 +856,12 @@ mod tests {
|
|||
let entries: Vec<_> = file_paths
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(i, path)| {
|
||||
(
|
||||
Arc::from(std::path::Path::new(path)),
|
||||
.filter_map(|(i, path)| {
|
||||
Some((
|
||||
Arc::from(RelPath::new(path).ok()?),
|
||||
ProjectEntryId::from_proto(i as u64 + 1),
|
||||
PathChange::Added,
|
||||
)
|
||||
))
|
||||
})
|
||||
.collect();
|
||||
let updated_entries: UpdatedEntriesSet = Arc::from(entries.as_slice());
|
||||
|
|
|
|||
|
|
@ -61,7 +61,8 @@ CREATE TABLE "projects" (
|
|||
"host_user_id" INTEGER REFERENCES users (id),
|
||||
"host_connection_id" INTEGER,
|
||||
"host_connection_server_id" INTEGER REFERENCES servers (id) ON DELETE CASCADE,
|
||||
"unregistered" BOOLEAN NOT NULL DEFAULT FALSE
|
||||
"unregistered" BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
"windows_paths" BOOLEAN NOT NULL DEFAULT FALSE
|
||||
);
|
||||
|
||||
CREATE INDEX "index_projects_on_host_connection_server_id" ON "projects" ("host_connection_server_id");
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE projects ADD COLUMN windows_paths BOOLEAN DEFAULT FALSE;
|
||||
|
|
@ -34,6 +34,7 @@ use std::{
|
|||
};
|
||||
use time::PrimitiveDateTime;
|
||||
use tokio::sync::{Mutex, OwnedMutexGuard};
|
||||
use util::paths::PathStyle;
|
||||
use worktree_settings_file::LocalSettingsKind;
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
@ -598,6 +599,7 @@ pub struct Project {
|
|||
pub worktrees: BTreeMap<u64, Worktree>,
|
||||
pub repositories: Vec<proto::UpdateRepository>,
|
||||
pub language_servers: Vec<LanguageServer>,
|
||||
pub path_style: PathStyle,
|
||||
}
|
||||
|
||||
pub struct ProjectCollaborator {
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ impl Database {
|
|||
connection: ConnectionId,
|
||||
worktrees: &[proto::WorktreeMetadata],
|
||||
is_ssh_project: bool,
|
||||
windows_paths: bool,
|
||||
) -> Result<TransactionGuard<(ProjectId, proto::Room)>> {
|
||||
self.room_transaction(room_id, |tx| async move {
|
||||
let participant = room_participant::Entity::find()
|
||||
|
|
@ -69,6 +70,7 @@ impl Database {
|
|||
connection.owner_id as i32,
|
||||
))),
|
||||
id: ActiveValue::NotSet,
|
||||
windows_paths: ActiveValue::set(windows_paths),
|
||||
}
|
||||
.insert(&*tx)
|
||||
.await?;
|
||||
|
|
@ -1046,6 +1048,12 @@ impl Database {
|
|||
.all(tx)
|
||||
.await?;
|
||||
|
||||
let path_style = if project.windows_paths {
|
||||
PathStyle::Windows
|
||||
} else {
|
||||
PathStyle::Posix
|
||||
};
|
||||
|
||||
let project = Project {
|
||||
id: project.id,
|
||||
role,
|
||||
|
|
@ -1073,6 +1081,7 @@ impl Database {
|
|||
capabilities: language_server.capabilities,
|
||||
})
|
||||
.collect(),
|
||||
path_style,
|
||||
};
|
||||
Ok((project, replica_id as ReplicaId))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ pub struct Model {
|
|||
pub host_user_id: Option<UserId>,
|
||||
pub host_connection_id: Option<i32>,
|
||||
pub host_connection_server_id: Option<ServerId>,
|
||||
pub windows_paths: bool,
|
||||
}
|
||||
|
||||
impl Model {
|
||||
|
|
|
|||
|
|
@ -558,18 +558,18 @@ async fn test_project_count(db: &Arc<Database>) {
|
|||
.unwrap();
|
||||
assert_eq!(db.project_count_excluding_admins().await.unwrap(), 0);
|
||||
|
||||
db.share_project(room_id, ConnectionId { owner_id, id: 1 }, &[], false)
|
||||
db.share_project(room_id, ConnectionId { owner_id, id: 1 }, &[], false, false)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(db.project_count_excluding_admins().await.unwrap(), 1);
|
||||
|
||||
db.share_project(room_id, ConnectionId { owner_id, id: 1 }, &[], false)
|
||||
db.share_project(room_id, ConnectionId { owner_id, id: 1 }, &[], false, false)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(db.project_count_excluding_admins().await.unwrap(), 2);
|
||||
|
||||
// Projects shared by admins aren't counted.
|
||||
db.share_project(room_id, ConnectionId { owner_id, id: 0 }, &[], false)
|
||||
db.share_project(room_id, ConnectionId { owner_id, id: 0 }, &[], false, false)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(db.project_count_excluding_admins().await.unwrap(), 2);
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ use reqwest_client::ReqwestClient;
|
|||
use rpc::proto::split_repository_update;
|
||||
use supermaven_api::{CreateExternalUserRequest, SupermavenAdminApi};
|
||||
use tracing::Span;
|
||||
use util::paths::PathStyle;
|
||||
|
||||
use futures::{
|
||||
FutureExt, SinkExt, StreamExt, TryStreamExt, channel::oneshot, future::BoxFuture,
|
||||
|
|
@ -1879,6 +1880,7 @@ async fn share_project(
|
|||
session.connection_id,
|
||||
&request.worktrees,
|
||||
request.is_ssh_project,
|
||||
request.windows_paths.unwrap_or(false),
|
||||
)
|
||||
.await?;
|
||||
response.send(proto::ShareProjectResponse {
|
||||
|
|
@ -2012,6 +2014,7 @@ async fn join_project(
|
|||
language_servers,
|
||||
language_server_capabilities,
|
||||
role: project.role.into(),
|
||||
windows_paths: project.path_style == PathStyle::Windows,
|
||||
})?;
|
||||
|
||||
for (worktree_id, worktree) in mem::take(&mut project.worktrees) {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ use gpui::{BackgroundExecutor, Context, Entity, TestAppContext, Window};
|
|||
use rpc::{RECEIVE_TIMEOUT, proto::PeerId};
|
||||
use serde_json::json;
|
||||
use std::ops::Range;
|
||||
use util::rel_path::rel_path;
|
||||
use workspace::CollaboratorId;
|
||||
|
||||
#[gpui::test]
|
||||
|
|
@ -256,7 +257,13 @@ async fn test_channel_notes_participant_indices(
|
|||
executor.start_waiting();
|
||||
let editor_a = workspace_a
|
||||
.update_in(cx_a, |workspace, window, cx| {
|
||||
workspace.open_path((worktree_id_a, "file.txt"), None, true, window, cx)
|
||||
workspace.open_path(
|
||||
(worktree_id_a, rel_path("file.txt")),
|
||||
None,
|
||||
true,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
|
|
@ -265,7 +272,13 @@ async fn test_channel_notes_participant_indices(
|
|||
executor.start_waiting();
|
||||
let editor_b = workspace_b
|
||||
.update_in(cx_b, |workspace, window, cx| {
|
||||
workspace.open_path((worktree_id_a, "file.txt"), None, true, window, cx)
|
||||
workspace.open_path(
|
||||
(worktree_id_a, rel_path("file.txt")),
|
||||
None,
|
||||
true,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ use chrono::Utc;
|
|||
use editor::Editor;
|
||||
use gpui::{BackgroundExecutor, TestAppContext};
|
||||
use rpc::proto;
|
||||
use util::rel_path::rel_path;
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_channel_guests(
|
||||
|
|
@ -55,7 +56,7 @@ async fn test_channel_guests(
|
|||
project_b
|
||||
.update(cx_b, |project, cx| {
|
||||
let worktree_id = project.worktrees(cx).next().unwrap().read(cx).id();
|
||||
project.create_entry((worktree_id, "b.txt"), false, cx)
|
||||
project.create_entry((worktree_id, rel_path("b.txt")), false, cx)
|
||||
})
|
||||
.await
|
||||
.is_err()
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ use editor::{
|
|||
};
|
||||
use fs::Fs;
|
||||
use futures::{SinkExt, StreamExt, channel::mpsc, lock::Mutex};
|
||||
use git::repository::repo_path;
|
||||
use gpui::{App, Rgba, TestAppContext, UpdateGlobal, VisualContext, VisualTestContext};
|
||||
use indoc::indoc;
|
||||
use language::FakeLspAdapter;
|
||||
|
|
@ -38,7 +39,7 @@ use std::{
|
|||
},
|
||||
};
|
||||
use text::Point;
|
||||
use util::{path, uri};
|
||||
use util::{path, rel_path::rel_path, uri};
|
||||
use workspace::{CloseIntent, Workspace};
|
||||
|
||||
#[gpui::test(iterations = 10)]
|
||||
|
|
@ -97,7 +98,7 @@ async fn test_host_disconnect(
|
|||
|
||||
let editor_b = workspace_b
|
||||
.update(cx_b, |workspace, window, cx| {
|
||||
workspace.open_path((worktree_id, "b.txt"), None, true, window, cx)
|
||||
workspace.open_path((worktree_id, rel_path("b.txt")), None, true, window, cx)
|
||||
})
|
||||
.unwrap()
|
||||
.await
|
||||
|
|
@ -205,7 +206,9 @@ async fn test_newline_above_or_below_does_not_move_guest_cursor(
|
|||
|
||||
// Open a buffer as client A
|
||||
let buffer_a = project_a
|
||||
.update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
|
||||
.update(cx_a, |p, cx| {
|
||||
p.open_buffer((worktree_id, rel_path("a.txt")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
let cx_a = cx_a.add_empty_window();
|
||||
|
|
@ -222,7 +225,9 @@ async fn test_newline_above_or_below_does_not_move_guest_cursor(
|
|||
let cx_b = cx_b.add_empty_window();
|
||||
// Open a buffer as client B
|
||||
let buffer_b = project_b
|
||||
.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
|
||||
.update(cx_b, |p, cx| {
|
||||
p.open_buffer((worktree_id, rel_path("a.txt")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
let editor_b = cx_b
|
||||
|
|
@ -334,7 +339,9 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
|
|||
|
||||
// Open a file in an editor as the guest.
|
||||
let buffer_b = project_b
|
||||
.update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
|
||||
.update(cx_b, |p, cx| {
|
||||
p.open_buffer((worktree_id, rel_path("main.rs")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
let cx_b = cx_b.add_empty_window();
|
||||
|
|
@ -408,7 +415,9 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
|
|||
|
||||
// Open the buffer on the host.
|
||||
let buffer_a = project_a
|
||||
.update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
|
||||
.update(cx_a, |p, cx| {
|
||||
p.open_buffer((worktree_id, rel_path("main.rs")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
cx_a.executor().run_until_parked();
|
||||
|
|
@ -599,7 +608,7 @@ async fn test_collaborating_with_code_actions(
|
|||
let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
|
||||
let editor_b = workspace_b
|
||||
.update_in(cx_b, |workspace, window, cx| {
|
||||
workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
|
||||
workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
|
|
@ -825,7 +834,7 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T
|
|||
let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
|
||||
let editor_b = workspace_b
|
||||
.update_in(cx_b, |workspace, window, cx| {
|
||||
workspace.open_path((worktree_id, "one.rs"), None, true, window, cx)
|
||||
workspace.open_path((worktree_id, rel_path("one.rs")), None, true, window, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
|
|
@ -1072,7 +1081,7 @@ async fn test_slow_lsp_server(cx_a: &mut TestAppContext, cx_b: &mut TestAppConte
|
|||
let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
|
||||
let editor_b = workspace_b
|
||||
.update_in(cx_b, |workspace, window, cx| {
|
||||
workspace.open_path((worktree_id, "one.rs"), None, true, window, cx)
|
||||
workspace.open_path((worktree_id, rel_path("one.rs")), None, true, window, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
|
|
@ -1412,7 +1421,10 @@ async fn test_share_project(
|
|||
project_b
|
||||
.update(cx_b, |project, cx| {
|
||||
let worktree = project.worktrees(cx).next().unwrap();
|
||||
let entry = worktree.read(cx).entry_for_path("ignored-dir").unwrap();
|
||||
let entry = worktree
|
||||
.read(cx)
|
||||
.entry_for_path(rel_path("ignored-dir"))
|
||||
.unwrap();
|
||||
project.expand_entry(worktree_id, entry.id, cx).unwrap()
|
||||
})
|
||||
.await
|
||||
|
|
@ -1435,17 +1447,21 @@ async fn test_share_project(
|
|||
|
||||
// Open the same file as client B and client A.
|
||||
let buffer_b = project_b
|
||||
.update(cx_b, |p, cx| p.open_buffer((worktree_id, "b.txt"), cx))
|
||||
.update(cx_b, |p, cx| {
|
||||
p.open_buffer((worktree_id, rel_path("b.txt")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
buffer_b.read_with(cx_b, |buf, _| assert_eq!(buf.text(), "b-contents"));
|
||||
|
||||
project_a.read_with(cx_a, |project, cx| {
|
||||
assert!(project.has_open_buffer((worktree_id, "b.txt"), cx))
|
||||
assert!(project.has_open_buffer((worktree_id, rel_path("b.txt")), cx))
|
||||
});
|
||||
let buffer_a = project_a
|
||||
.update(cx_a, |p, cx| p.open_buffer((worktree_id, "b.txt"), cx))
|
||||
.update(cx_a, |p, cx| {
|
||||
p.open_buffer((worktree_id, rel_path("b.txt")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
|
@ -1553,7 +1569,9 @@ async fn test_on_input_format_from_host_to_guest(
|
|||
|
||||
// Open a file in an editor as the host.
|
||||
let buffer_a = project_a
|
||||
.update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
|
||||
.update(cx_a, |p, cx| {
|
||||
p.open_buffer((worktree_id, rel_path("main.rs")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
let cx_a = cx_a.add_empty_window();
|
||||
|
|
@ -1586,7 +1604,9 @@ async fn test_on_input_format_from_host_to_guest(
|
|||
|
||||
// Open the buffer on the guest and see that the formatting worked
|
||||
let buffer_b = project_b
|
||||
.update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
|
||||
.update(cx_b, |p, cx| {
|
||||
p.open_buffer((worktree_id, rel_path("main.rs")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
|
@ -1686,7 +1706,9 @@ async fn test_on_input_format_from_guest_to_host(
|
|||
|
||||
// Open a file in an editor as the guest.
|
||||
let buffer_b = project_b
|
||||
.update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
|
||||
.update(cx_b, |p, cx| {
|
||||
p.open_buffer((worktree_id, rel_path("main.rs")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
let cx_b = cx_b.add_empty_window();
|
||||
|
|
@ -1732,7 +1754,9 @@ async fn test_on_input_format_from_guest_to_host(
|
|||
|
||||
// Open the buffer on the host and see that the formatting worked
|
||||
let buffer_a = project_a
|
||||
.update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
|
||||
.update(cx_a, |p, cx| {
|
||||
p.open_buffer((worktree_id, rel_path("main.rs")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
executor.run_until_parked();
|
||||
|
|
@ -1881,7 +1905,7 @@ async fn test_mutual_editor_inlay_hint_cache_update(
|
|||
.unwrap();
|
||||
let editor_a = workspace_a
|
||||
.update_in(cx_a, |workspace, window, cx| {
|
||||
workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
|
||||
workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
|
|
@ -1931,7 +1955,7 @@ async fn test_mutual_editor_inlay_hint_cache_update(
|
|||
let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
|
||||
let editor_b = workspace_b
|
||||
.update_in(cx_b, |workspace, window, cx| {
|
||||
workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
|
||||
workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
|
|
@ -2126,7 +2150,7 @@ async fn test_inlay_hint_refresh_is_forwarded(
|
|||
|
||||
let editor_a = workspace_a
|
||||
.update_in(cx_a, |workspace, window, cx| {
|
||||
workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
|
||||
workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
|
|
@ -2135,7 +2159,7 @@ async fn test_inlay_hint_refresh_is_forwarded(
|
|||
|
||||
let editor_b = workspace_b
|
||||
.update_in(cx_b, |workspace, window, cx| {
|
||||
workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
|
||||
workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
|
|
@ -2313,7 +2337,7 @@ async fn test_lsp_document_color(cx_a: &mut TestAppContext, cx_b: &mut TestAppCo
|
|||
.unwrap();
|
||||
let editor_a = workspace_a
|
||||
.update_in(cx_a, |workspace, window, cx| {
|
||||
workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
|
||||
workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
|
|
@ -2373,7 +2397,7 @@ async fn test_lsp_document_color(cx_a: &mut TestAppContext, cx_b: &mut TestAppCo
|
|||
let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
|
||||
let editor_b = workspace_b
|
||||
.update_in(cx_b, |workspace, window, cx| {
|
||||
workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
|
||||
workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
|
|
@ -2594,7 +2618,7 @@ async fn test_lsp_pull_diagnostics(
|
|||
.unwrap();
|
||||
let editor_a_main = workspace_a
|
||||
.update_in(cx_a, |workspace, window, cx| {
|
||||
workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
|
||||
workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
|
|
@ -2953,7 +2977,7 @@ async fn test_lsp_pull_diagnostics(
|
|||
let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
|
||||
let editor_b_main = workspace_b
|
||||
.update_in(cx_b, |workspace, window, cx| {
|
||||
workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
|
||||
workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
|
|
@ -3001,7 +3025,7 @@ async fn test_lsp_pull_diagnostics(
|
|||
|
||||
let editor_b_lib = workspace_b
|
||||
.update_in(cx_b, |workspace, window, cx| {
|
||||
workspace.open_path((worktree_id, "lib.rs"), None, true, window, cx)
|
||||
workspace.open_path((worktree_id, rel_path("lib.rs")), None, true, window, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
|
|
@ -3355,7 +3379,7 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
|
|||
};
|
||||
client_a.fs().set_blame_for_repo(
|
||||
Path::new(path!("/my-repo/.git")),
|
||||
vec![("file.txt".into(), blame)],
|
||||
vec![(repo_path("file.txt"), blame)],
|
||||
);
|
||||
|
||||
let (project_a, worktree_id) = client_a.build_local_project(path!("/my-repo"), cx_a).await;
|
||||
|
|
@ -3368,7 +3392,7 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
|
|||
let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
|
||||
let editor_a = workspace_a
|
||||
.update_in(cx_a, |workspace, window, cx| {
|
||||
workspace.open_path((worktree_id, "file.txt"), None, true, window, cx)
|
||||
workspace.open_path((worktree_id, rel_path("file.txt")), None, true, window, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
|
|
@ -3380,7 +3404,7 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
|
|||
let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
|
||||
let editor_b = workspace_b
|
||||
.update_in(cx_b, |workspace, window, cx| {
|
||||
workspace.open_path((worktree_id, "file.txt"), None, true, window, cx)
|
||||
workspace.open_path((worktree_id, rel_path("file.txt")), None, true, window, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
|
|
@ -3558,13 +3582,13 @@ async fn test_collaborating_with_editorconfig(
|
|||
.unwrap();
|
||||
let main_buffer_a = project_a
|
||||
.update(cx_a, |p, cx| {
|
||||
p.open_buffer((worktree_id, "src/main.rs"), cx)
|
||||
p.open_buffer((worktree_id, rel_path("src/main.rs")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
let other_buffer_a = project_a
|
||||
.update(cx_a, |p, cx| {
|
||||
p.open_buffer((worktree_id, "src/other_mod/other.rs"), cx)
|
||||
p.open_buffer((worktree_id, rel_path("src/other_mod/other.rs")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
|
@ -3592,13 +3616,13 @@ async fn test_collaborating_with_editorconfig(
|
|||
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
||||
let main_buffer_b = project_b
|
||||
.update(cx_b, |p, cx| {
|
||||
p.open_buffer((worktree_id, "src/main.rs"), cx)
|
||||
p.open_buffer((worktree_id, rel_path("src/main.rs")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
let other_buffer_b = project_b
|
||||
.update(cx_b, |p, cx| {
|
||||
p.open_buffer((worktree_id, "src/other_mod/other.rs"), cx)
|
||||
p.open_buffer((worktree_id, rel_path("src/other_mod/other.rs")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
|
@ -3717,7 +3741,7 @@ fn main() { let foo = other::foo(); }"};
|
|||
|
||||
let editorconfig_buffer_b = project_b
|
||||
.update(cx_b, |p, cx| {
|
||||
p.open_buffer((worktree_id, "src/other_mod/.editorconfig"), cx)
|
||||
p.open_buffer((worktree_id, rel_path("src/other_mod/.editorconfig")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
|
@ -3794,7 +3818,7 @@ async fn test_add_breakpoints(cx_a: &mut TestAppContext, cx_b: &mut TestAppConte
|
|||
let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
|
||||
let project_path = ProjectPath {
|
||||
worktree_id,
|
||||
path: Arc::from(Path::new(&"test.txt")),
|
||||
path: rel_path(&"test.txt").into(),
|
||||
};
|
||||
let abs_path = project_a.read_with(cx_a, |project, cx| {
|
||||
project
|
||||
|
|
@ -4017,7 +4041,7 @@ async fn test_client_can_query_lsp_ext(cx_a: &mut TestAppContext, cx_b: &mut Tes
|
|||
|
||||
let editor_a = workspace_a
|
||||
.update_in(cx_a, |workspace, window, cx| {
|
||||
workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
|
||||
workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
|
|
@ -4026,7 +4050,7 @@ async fn test_client_can_query_lsp_ext(cx_a: &mut TestAppContext, cx_b: &mut Tes
|
|||
|
||||
let editor_b = workspace_b
|
||||
.update_in(cx_b, |workspace, window, cx| {
|
||||
workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
|
||||
workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ use rpc::proto::PeerId;
|
|||
use serde_json::json;
|
||||
use settings::SettingsStore;
|
||||
use text::{Point, ToPoint};
|
||||
use util::{path, test::sample_text};
|
||||
use util::{path, rel_path::rel_path, test::sample_text};
|
||||
use workspace::{CollaboratorId, SplitDirection, Workspace, item::ItemHandle as _};
|
||||
|
||||
use super::TestClient;
|
||||
|
|
@ -86,7 +86,7 @@ async fn test_basic_following(
|
|||
let pane_a = workspace_a.update(cx_a, |workspace, _| workspace.active_pane().clone());
|
||||
let editor_a1 = workspace_a
|
||||
.update_in(cx_a, |workspace, window, cx| {
|
||||
workspace.open_path((worktree_id, "1.txt"), None, true, window, cx)
|
||||
workspace.open_path((worktree_id, rel_path("1.txt")), None, true, window, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
|
|
@ -94,7 +94,7 @@ async fn test_basic_following(
|
|||
.unwrap();
|
||||
let editor_a2 = workspace_a
|
||||
.update_in(cx_a, |workspace, window, cx| {
|
||||
workspace.open_path((worktree_id, "2.txt"), None, true, window, cx)
|
||||
workspace.open_path((worktree_id, rel_path("2.txt")), None, true, window, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
|
|
@ -104,7 +104,7 @@ async fn test_basic_following(
|
|||
// Client B opens an editor.
|
||||
let editor_b1 = workspace_b
|
||||
.update_in(cx_b, |workspace, window, cx| {
|
||||
workspace.open_path((worktree_id, "1.txt"), None, true, window, cx)
|
||||
workspace.open_path((worktree_id, rel_path("1.txt")), None, true, window, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
|
|
@ -146,7 +146,7 @@ async fn test_basic_following(
|
|||
});
|
||||
assert_eq!(
|
||||
cx_b.read(|cx| editor_b2.project_path(cx)),
|
||||
Some((worktree_id, "2.txt").into())
|
||||
Some((worktree_id, rel_path("2.txt")).into())
|
||||
);
|
||||
assert_eq!(
|
||||
editor_b2.update(cx_b, |editor, cx| editor.selections.ranges(cx)),
|
||||
|
|
@ -286,12 +286,12 @@ async fn test_basic_following(
|
|||
let multibuffer_a = cx_a.new(|cx| {
|
||||
let buffer_a1 = project_a.update(cx, |project, cx| {
|
||||
project
|
||||
.get_open_buffer(&(worktree_id, "1.txt").into(), cx)
|
||||
.get_open_buffer(&(worktree_id, rel_path("1.txt")).into(), cx)
|
||||
.unwrap()
|
||||
});
|
||||
let buffer_a2 = project_a.update(cx, |project, cx| {
|
||||
project
|
||||
.get_open_buffer(&(worktree_id, "2.txt").into(), cx)
|
||||
.get_open_buffer(&(worktree_id, rel_path("2.txt")).into(), cx)
|
||||
.unwrap()
|
||||
});
|
||||
let mut result = MultiBuffer::new(Capability::ReadWrite);
|
||||
|
|
@ -618,13 +618,13 @@ async fn test_following_tab_order(
|
|||
//Open 1, 3 in that order on client A
|
||||
workspace_a
|
||||
.update_in(cx_a, |workspace, window, cx| {
|
||||
workspace.open_path((worktree_id, "1.txt"), None, true, window, cx)
|
||||
workspace.open_path((worktree_id, rel_path("1.txt")), None, true, window, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
workspace_a
|
||||
.update_in(cx_a, |workspace, window, cx| {
|
||||
workspace.open_path((worktree_id, "3.txt"), None, true, window, cx)
|
||||
workspace.open_path((worktree_id, rel_path("3.txt")), None, true, window, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
|
@ -632,14 +632,7 @@ async fn test_following_tab_order(
|
|||
let pane_paths = |pane: &Entity<workspace::Pane>, cx: &mut VisualTestContext| {
|
||||
pane.update(cx, |pane, cx| {
|
||||
pane.items()
|
||||
.map(|item| {
|
||||
item.project_path(cx)
|
||||
.unwrap()
|
||||
.path
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_owned()
|
||||
})
|
||||
.map(|item| item.project_path(cx).unwrap().path.as_str().to_owned())
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
};
|
||||
|
|
@ -656,7 +649,7 @@ async fn test_following_tab_order(
|
|||
//Open just 2 on client B
|
||||
workspace_b
|
||||
.update_in(cx_b, |workspace, window, cx| {
|
||||
workspace.open_path((worktree_id, "2.txt"), None, true, window, cx)
|
||||
workspace.open_path((worktree_id, rel_path("2.txt")), None, true, window, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
|
@ -668,7 +661,7 @@ async fn test_following_tab_order(
|
|||
//Open just 1 on client B
|
||||
workspace_b
|
||||
.update_in(cx_b, |workspace, window, cx| {
|
||||
workspace.open_path((worktree_id, "1.txt"), None, true, window, cx)
|
||||
workspace.open_path((worktree_id, rel_path("1.txt")), None, true, window, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
|
@ -728,7 +721,7 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T
|
|||
let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
|
||||
workspace_a
|
||||
.update_in(cx_a, |workspace, window, cx| {
|
||||
workspace.open_path((worktree_id, "1.txt"), None, true, window, cx)
|
||||
workspace.open_path((worktree_id, rel_path("1.txt")), None, true, window, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
|
|
@ -739,7 +732,7 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T
|
|||
let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
|
||||
workspace_b
|
||||
.update_in(cx_b, |workspace, window, cx| {
|
||||
workspace.open_path((worktree_id, "2.txt"), None, true, window, cx)
|
||||
workspace.open_path((worktree_id, rel_path("2.txt")), None, true, window, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
|
|
@ -816,14 +809,14 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T
|
|||
// Clients A and B each open a new file.
|
||||
workspace_a
|
||||
.update_in(cx_a, |workspace, window, cx| {
|
||||
workspace.open_path((worktree_id, "3.txt"), None, true, window, cx)
|
||||
workspace.open_path((worktree_id, rel_path("3.txt")), None, true, window, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
workspace_b
|
||||
.update_in(cx_b, |workspace, window, cx| {
|
||||
workspace.open_path((worktree_id, "4.txt"), None, true, window, cx)
|
||||
workspace.open_path((worktree_id, rel_path("4.txt")), None, true, window, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
|
@ -1259,7 +1252,7 @@ async fn test_auto_unfollowing(cx_a: &mut TestAppContext, cx_b: &mut TestAppCont
|
|||
|
||||
let _editor_a1 = workspace_a
|
||||
.update_in(cx_a, |workspace, window, cx| {
|
||||
workspace.open_path((worktree_id, "1.txt"), None, true, window, cx)
|
||||
workspace.open_path((worktree_id, rel_path("1.txt")), None, true, window, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
|
|
@ -1359,7 +1352,7 @@ async fn test_auto_unfollowing(cx_a: &mut TestAppContext, cx_b: &mut TestAppCont
|
|||
// When client B activates a different item in the original pane, it automatically stops following client A.
|
||||
workspace_b
|
||||
.update_in(cx_b, |workspace, window, cx| {
|
||||
workspace.open_path((worktree_id, "2.txt"), None, true, window, cx)
|
||||
workspace.open_path((worktree_id, rel_path("2.txt")), None, true, window, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
|
@ -1492,7 +1485,7 @@ async fn test_following_across_workspaces(cx_a: &mut TestAppContext, cx_b: &mut
|
|||
|
||||
workspace_a
|
||||
.update_in(cx_a, |workspace, window, cx| {
|
||||
workspace.open_path((worktree_id_a, "w.rs"), None, true, window, cx)
|
||||
workspace.open_path((worktree_id_a, rel_path("w.rs")), None, true, window, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
|
@ -1545,7 +1538,7 @@ async fn test_following_across_workspaces(cx_a: &mut TestAppContext, cx_b: &mut
|
|||
// b moves to x.rs in a's project, and a follows
|
||||
workspace_b_project_a
|
||||
.update_in(&mut cx_b2, |workspace, window, cx| {
|
||||
workspace.open_path((worktree_id_a, "x.rs"), None, true, window, cx)
|
||||
workspace.open_path((worktree_id_a, rel_path("x.rs")), None, true, window, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
|
@ -1574,7 +1567,7 @@ async fn test_following_across_workspaces(cx_a: &mut TestAppContext, cx_b: &mut
|
|||
// b moves to y.rs in b's project, a is still following but can't yet see
|
||||
workspace_b
|
||||
.update_in(cx_b, |workspace, window, cx| {
|
||||
workspace.open_path((worktree_id_b, "y.rs"), None, true, window, cx)
|
||||
workspace.open_path((worktree_id_b, rel_path("y.rs")), None, true, window, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
|
@ -1759,7 +1752,7 @@ async fn test_following_into_excluded_file(
|
|||
// Client A opens editors for a regular file and an excluded file.
|
||||
let editor_for_regular = workspace_a
|
||||
.update_in(cx_a, |workspace, window, cx| {
|
||||
workspace.open_path((worktree_id, "1.txt"), None, true, window, cx)
|
||||
workspace.open_path((worktree_id, rel_path("1.txt")), None, true, window, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
|
|
@ -1767,7 +1760,13 @@ async fn test_following_into_excluded_file(
|
|||
.unwrap();
|
||||
let editor_for_excluded_a = workspace_a
|
||||
.update_in(cx_a, |workspace, window, cx| {
|
||||
workspace.open_path((worktree_id, ".git/COMMIT_EDITMSG"), None, true, window, cx)
|
||||
workspace.open_path(
|
||||
(worktree_id, rel_path(".git/COMMIT_EDITMSG")),
|
||||
None,
|
||||
true,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
|
|
@ -1805,7 +1804,7 @@ async fn test_following_into_excluded_file(
|
|||
});
|
||||
assert_eq!(
|
||||
cx_b.read(|cx| editor_for_excluded_b.project_path(cx)),
|
||||
Some((worktree_id, ".git/COMMIT_EDITMSG").into())
|
||||
Some((worktree_id, rel_path(".git/COMMIT_EDITMSG")).into())
|
||||
);
|
||||
assert_eq!(
|
||||
editor_for_excluded_b.update(cx_b, |editor, cx| editor.selections.ranges(cx)),
|
||||
|
|
@ -2051,7 +2050,7 @@ async fn test_following_to_channel_notes_without_a_shared_project(
|
|||
// Client A opens a local buffer in their unshared project.
|
||||
let _unshared_editor_a1 = workspace_a
|
||||
.update_in(cx_a, |workspace, window, cx| {
|
||||
workspace.open_path((worktree_id, "1.txt"), None, true, window, cx)
|
||||
workspace.open_path((worktree_id, rel_path("1.txt")), None, true, window, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
|
|
|
|||
|
|
@ -1,7 +1,4 @@
|
|||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
use std::path::Path;
|
||||
|
||||
use call::ActiveCall;
|
||||
use git::status::{FileStatus, StatusCode, TrackedStatus};
|
||||
|
|
@ -9,7 +6,7 @@ use git_ui::project_diff::ProjectDiff;
|
|||
use gpui::{TestAppContext, VisualTestContext};
|
||||
use project::ProjectPath;
|
||||
use serde_json::json;
|
||||
use util::path;
|
||||
use util::{path, rel_path::rel_path};
|
||||
use workspace::Workspace;
|
||||
|
||||
//
|
||||
|
|
@ -41,13 +38,13 @@ async fn test_project_diff(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext)
|
|||
)
|
||||
.await;
|
||||
|
||||
client_a.fs().set_git_content_for_repo(
|
||||
client_a.fs().set_head_and_index_for_repo(
|
||||
Path::new(path!("/a/.git")),
|
||||
&[
|
||||
("changed.txt".into(), "before\n".to_string(), None),
|
||||
("unchanged.txt".into(), "unchanged\n".to_string(), None),
|
||||
("deleted.txt".into(), "deleted\n".to_string(), None),
|
||||
("secret.pem".into(), "shh\n".to_string(), None),
|
||||
("changed.txt", "before\n".to_string()),
|
||||
("unchanged.txt", "unchanged\n".to_string()),
|
||||
("deleted.txt", "deleted\n".to_string()),
|
||||
("secret.pem", "shh\n".to_string()),
|
||||
],
|
||||
);
|
||||
let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
|
||||
|
|
@ -109,7 +106,7 @@ async fn test_project_diff(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext)
|
|||
project_b.update(cx_b, |project, cx| {
|
||||
let project_path = ProjectPath {
|
||||
worktree_id,
|
||||
path: Arc::from(PathBuf::from("unchanged.txt")),
|
||||
path: rel_path("unchanged.txt").into(),
|
||||
};
|
||||
let status = project.project_path_git_status(&project_path, cx);
|
||||
assert_eq!(
|
||||
|
|
|
|||
|
|
@ -14,7 +14,10 @@ use client::{RECEIVE_TIMEOUT, User};
|
|||
use collections::{HashMap, HashSet};
|
||||
use fs::{FakeFs, Fs as _, RemoveOptions};
|
||||
use futures::{StreamExt as _, channel::mpsc};
|
||||
use git::status::{FileStatus, StatusCode, TrackedStatus, UnmergedStatus, UnmergedStatusCode};
|
||||
use git::{
|
||||
repository::repo_path,
|
||||
status::{FileStatus, StatusCode, TrackedStatus, UnmergedStatus, UnmergedStatusCode},
|
||||
};
|
||||
use gpui::{
|
||||
App, BackgroundExecutor, Entity, Modifiers, MouseButton, MouseDownEvent, TestAppContext,
|
||||
UpdateGlobal, px, size,
|
||||
|
|
@ -30,7 +33,7 @@ use parking_lot::Mutex;
|
|||
use pretty_assertions::assert_eq;
|
||||
use project::{
|
||||
DiagnosticSummary, HoverBlockKind, Project, ProjectPath,
|
||||
lsp_store::{FormatTrigger, LspFormatTarget},
|
||||
lsp_store::{FormatTrigger, LspFormatTarget, SymbolLocation},
|
||||
search::{SearchQuery, SearchResult},
|
||||
};
|
||||
use prompt_store::PromptBuilder;
|
||||
|
|
@ -49,7 +52,7 @@ use std::{
|
|||
time::Duration,
|
||||
};
|
||||
use unindent::Unindent as _;
|
||||
use util::{path, uri};
|
||||
use util::{path, rel_path::rel_path, uri};
|
||||
use workspace::Pane;
|
||||
|
||||
#[ctor::ctor]
|
||||
|
|
@ -1418,7 +1421,9 @@ async fn test_unshare_project(
|
|||
assert!(worktree_a.read_with(cx_a, |tree, _| tree.has_update_observer()));
|
||||
|
||||
project_b
|
||||
.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
|
||||
.update(cx_b, |p, cx| {
|
||||
p.open_buffer((worktree_id, rel_path("a.txt")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
|
@ -1454,7 +1459,9 @@ async fn test_unshare_project(
|
|||
|
||||
assert!(worktree_a.read_with(cx_a, |tree, _| tree.has_update_observer()));
|
||||
project_c2
|
||||
.update(cx_c, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
|
||||
.update(cx_c, |p, cx| {
|
||||
p.open_buffer((worktree_id, rel_path("a.txt")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
|
@ -1584,11 +1591,15 @@ async fn test_project_reconnect(
|
|||
});
|
||||
|
||||
let buffer_a1 = project_a1
|
||||
.update(cx_a, |p, cx| p.open_buffer((worktree1_id, "a.txt"), cx))
|
||||
.update(cx_a, |p, cx| {
|
||||
p.open_buffer((worktree1_id, rel_path("a.txt")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
let buffer_b1 = project_b1
|
||||
.update(cx_b, |p, cx| p.open_buffer((worktree1_id, "a.txt"), cx))
|
||||
.update(cx_b, |p, cx| {
|
||||
p.open_buffer((worktree1_id, rel_path("a.txt")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
|
@ -1675,20 +1686,15 @@ async fn test_project_reconnect(
|
|||
assert!(project.is_shared());
|
||||
assert!(worktree_a1.read(cx).has_update_observer());
|
||||
assert_eq!(
|
||||
worktree_a1
|
||||
.read(cx)
|
||||
.snapshot()
|
||||
.paths()
|
||||
.map(|p| p.to_str().unwrap())
|
||||
.collect::<Vec<_>>(),
|
||||
worktree_a1.read(cx).snapshot().paths().collect::<Vec<_>>(),
|
||||
vec![
|
||||
path!("a.txt"),
|
||||
path!("b.txt"),
|
||||
path!("subdir2"),
|
||||
path!("subdir2/f.txt"),
|
||||
path!("subdir2/g.txt"),
|
||||
path!("subdir2/h.txt"),
|
||||
path!("subdir2/i.txt")
|
||||
rel_path("a.txt"),
|
||||
rel_path("b.txt"),
|
||||
rel_path("subdir2"),
|
||||
rel_path("subdir2/f.txt"),
|
||||
rel_path("subdir2/g.txt"),
|
||||
rel_path("subdir2/h.txt"),
|
||||
rel_path("subdir2/i.txt")
|
||||
]
|
||||
);
|
||||
assert!(worktree_a3.read(cx).has_update_observer());
|
||||
|
|
@ -1697,7 +1703,7 @@ async fn test_project_reconnect(
|
|||
.read(cx)
|
||||
.snapshot()
|
||||
.paths()
|
||||
.map(|p| p.to_str().unwrap())
|
||||
.map(|p| p.as_str())
|
||||
.collect::<Vec<_>>(),
|
||||
vec!["w.txt", "x.txt", "y.txt"]
|
||||
);
|
||||
|
|
@ -1712,16 +1718,15 @@ async fn test_project_reconnect(
|
|||
.read(cx)
|
||||
.snapshot()
|
||||
.paths()
|
||||
.map(|p| p.to_str().unwrap())
|
||||
.collect::<Vec<_>>(),
|
||||
vec![
|
||||
path!("a.txt"),
|
||||
path!("b.txt"),
|
||||
path!("subdir2"),
|
||||
path!("subdir2/f.txt"),
|
||||
path!("subdir2/g.txt"),
|
||||
path!("subdir2/h.txt"),
|
||||
path!("subdir2/i.txt")
|
||||
rel_path("a.txt"),
|
||||
rel_path("b.txt"),
|
||||
rel_path("subdir2"),
|
||||
rel_path("subdir2/f.txt"),
|
||||
rel_path("subdir2/g.txt"),
|
||||
rel_path("subdir2/h.txt"),
|
||||
rel_path("subdir2/i.txt")
|
||||
]
|
||||
);
|
||||
assert!(project.worktree_for_id(worktree2_id, cx).is_none());
|
||||
|
|
@ -1732,7 +1737,7 @@ async fn test_project_reconnect(
|
|||
.read(cx)
|
||||
.snapshot()
|
||||
.paths()
|
||||
.map(|p| p.to_str().unwrap())
|
||||
.map(|p| p.as_str())
|
||||
.collect::<Vec<_>>(),
|
||||
vec!["w.txt", "x.txt", "y.txt"]
|
||||
);
|
||||
|
|
@ -1809,16 +1814,15 @@ async fn test_project_reconnect(
|
|||
.read(cx)
|
||||
.snapshot()
|
||||
.paths()
|
||||
.map(|p| p.to_str().unwrap())
|
||||
.collect::<Vec<_>>(),
|
||||
vec![
|
||||
path!("a.txt"),
|
||||
path!("b.txt"),
|
||||
path!("subdir2"),
|
||||
path!("subdir2/f.txt"),
|
||||
path!("subdir2/g.txt"),
|
||||
path!("subdir2/h.txt"),
|
||||
path!("subdir2/j.txt")
|
||||
rel_path("a.txt"),
|
||||
rel_path("b.txt"),
|
||||
rel_path("subdir2"),
|
||||
rel_path("subdir2/f.txt"),
|
||||
rel_path("subdir2/g.txt"),
|
||||
rel_path("subdir2/h.txt"),
|
||||
rel_path("subdir2/j.txt")
|
||||
]
|
||||
);
|
||||
assert!(project.worktree_for_id(worktree2_id, cx).is_none());
|
||||
|
|
@ -1829,7 +1833,7 @@ async fn test_project_reconnect(
|
|||
.read(cx)
|
||||
.snapshot()
|
||||
.paths()
|
||||
.map(|p| p.to_str().unwrap())
|
||||
.map(|p| p.as_str())
|
||||
.collect::<Vec<_>>(),
|
||||
vec!["z.txt"]
|
||||
);
|
||||
|
|
@ -2370,11 +2374,15 @@ async fn test_propagate_saves_and_fs_changes(
|
|||
|
||||
// Open and edit a buffer as both guests B and C.
|
||||
let buffer_b = project_b
|
||||
.update(cx_b, |p, cx| p.open_buffer((worktree_id, "file1.rs"), cx))
|
||||
.update(cx_b, |p, cx| {
|
||||
p.open_buffer((worktree_id, rel_path("file1.rs")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
let buffer_c = project_c
|
||||
.update(cx_c, |p, cx| p.open_buffer((worktree_id, "file1.rs"), cx))
|
||||
.update(cx_c, |p, cx| {
|
||||
p.open_buffer((worktree_id, rel_path("file1.rs")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
|
@ -2390,7 +2398,9 @@ async fn test_propagate_saves_and_fs_changes(
|
|||
|
||||
// Open and edit that buffer as the host.
|
||||
let buffer_a = project_a
|
||||
.update(cx_a, |p, cx| p.open_buffer((worktree_id, "file1.rs"), cx))
|
||||
.update(cx_a, |p, cx| {
|
||||
p.open_buffer((worktree_id, rel_path("file1.rs")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
|
@ -2461,27 +2471,21 @@ async fn test_propagate_saves_and_fs_changes(
|
|||
|
||||
worktree_a.read_with(cx_a, |tree, _| {
|
||||
assert_eq!(
|
||||
tree.paths()
|
||||
.map(|p| p.to_string_lossy())
|
||||
.collect::<Vec<_>>(),
|
||||
tree.paths().map(|p| p.as_str()).collect::<Vec<_>>(),
|
||||
["file1.js", "file3", "file4"]
|
||||
)
|
||||
});
|
||||
|
||||
worktree_b.read_with(cx_b, |tree, _| {
|
||||
assert_eq!(
|
||||
tree.paths()
|
||||
.map(|p| p.to_string_lossy())
|
||||
.collect::<Vec<_>>(),
|
||||
tree.paths().map(|p| p.as_str()).collect::<Vec<_>>(),
|
||||
["file1.js", "file3", "file4"]
|
||||
)
|
||||
});
|
||||
|
||||
worktree_c.read_with(cx_c, |tree, _| {
|
||||
assert_eq!(
|
||||
tree.paths()
|
||||
.map(|p| p.to_string_lossy())
|
||||
.collect::<Vec<_>>(),
|
||||
tree.paths().map(|p| p.as_str()).collect::<Vec<_>>(),
|
||||
["file1.js", "file3", "file4"]
|
||||
)
|
||||
});
|
||||
|
|
@ -2489,17 +2493,17 @@ async fn test_propagate_saves_and_fs_changes(
|
|||
// Ensure buffer files are updated as well.
|
||||
|
||||
buffer_a.read_with(cx_a, |buffer, _| {
|
||||
assert_eq!(buffer.file().unwrap().path().to_str(), Some("file1.js"));
|
||||
assert_eq!(buffer.file().unwrap().path().as_str(), "file1.js");
|
||||
assert_eq!(buffer.language().unwrap().name(), "JavaScript".into());
|
||||
});
|
||||
|
||||
buffer_b.read_with(cx_b, |buffer, _| {
|
||||
assert_eq!(buffer.file().unwrap().path().to_str(), Some("file1.js"));
|
||||
assert_eq!(buffer.file().unwrap().path().as_str(), "file1.js");
|
||||
assert_eq!(buffer.language().unwrap().name(), "JavaScript".into());
|
||||
});
|
||||
|
||||
buffer_c.read_with(cx_c, |buffer, _| {
|
||||
assert_eq!(buffer.file().unwrap().path().to_str(), Some("file1.js"));
|
||||
assert_eq!(buffer.file().unwrap().path().as_str(), "file1.js");
|
||||
assert_eq!(buffer.language().unwrap().name(), "JavaScript".into());
|
||||
});
|
||||
|
||||
|
|
@ -2524,7 +2528,7 @@ async fn test_propagate_saves_and_fs_changes(
|
|||
project_a
|
||||
.update(cx_a, |project, cx| {
|
||||
let path = ProjectPath {
|
||||
path: Arc::from(Path::new("file3.rs")),
|
||||
path: rel_path("file3.rs").into(),
|
||||
worktree_id: worktree_a.read(cx).id(),
|
||||
};
|
||||
|
||||
|
|
@ -2538,7 +2542,7 @@ async fn test_propagate_saves_and_fs_changes(
|
|||
new_buffer_b.read_with(cx_b, |buffer_b, _| {
|
||||
assert_eq!(
|
||||
buffer_b.file().unwrap().path().as_ref(),
|
||||
Path::new("file3.rs")
|
||||
rel_path("file3.rs")
|
||||
);
|
||||
|
||||
new_buffer_a.read_with(cx_a, |buffer_a, _| {
|
||||
|
|
@ -2621,19 +2625,20 @@ async fn test_git_diff_base_change(
|
|||
"
|
||||
.unindent();
|
||||
|
||||
client_a.fs().set_index_for_repo(
|
||||
Path::new("/dir/.git"),
|
||||
&[("a.txt".into(), staged_text.clone())],
|
||||
);
|
||||
client_a
|
||||
.fs()
|
||||
.set_index_for_repo(Path::new("/dir/.git"), &[("a.txt", staged_text.clone())]);
|
||||
client_a.fs().set_head_for_repo(
|
||||
Path::new("/dir/.git"),
|
||||
&[("a.txt".into(), committed_text.clone())],
|
||||
&[("a.txt", committed_text.clone())],
|
||||
"deadbeef",
|
||||
);
|
||||
|
||||
// Create the buffer
|
||||
let buffer_local_a = project_local
|
||||
.update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
|
||||
.update(cx_a, |p, cx| {
|
||||
p.open_buffer((worktree_id, rel_path("a.txt")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
let local_unstaged_diff_a = project_local
|
||||
|
|
@ -2661,7 +2666,9 @@ async fn test_git_diff_base_change(
|
|||
|
||||
// Create remote buffer
|
||||
let remote_buffer_a = project_remote
|
||||
.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
|
||||
.update(cx_b, |p, cx| {
|
||||
p.open_buffer((worktree_id, rel_path("a.txt")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
let remote_unstaged_diff_a = project_remote
|
||||
|
|
@ -2717,11 +2724,11 @@ async fn test_git_diff_base_change(
|
|||
// Update the index text of the open buffer
|
||||
client_a.fs().set_index_for_repo(
|
||||
Path::new("/dir/.git"),
|
||||
&[("a.txt".into(), new_staged_text.clone())],
|
||||
&[("a.txt", new_staged_text.clone())],
|
||||
);
|
||||
client_a.fs().set_head_for_repo(
|
||||
Path::new("/dir/.git"),
|
||||
&[("a.txt".into(), new_committed_text.clone())],
|
||||
&[("a.txt", new_committed_text.clone())],
|
||||
"deadbeef",
|
||||
);
|
||||
|
||||
|
|
@ -2790,12 +2797,14 @@ async fn test_git_diff_base_change(
|
|||
|
||||
client_a.fs().set_index_for_repo(
|
||||
Path::new("/dir/sub/.git"),
|
||||
&[("b.txt".into(), staged_text.clone())],
|
||||
&[("b.txt", staged_text.clone())],
|
||||
);
|
||||
|
||||
// Create the buffer
|
||||
let buffer_local_b = project_local
|
||||
.update(cx_a, |p, cx| p.open_buffer((worktree_id, "sub/b.txt"), cx))
|
||||
.update(cx_a, |p, cx| {
|
||||
p.open_buffer((worktree_id, rel_path("sub/b.txt")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
let local_unstaged_diff_b = project_local
|
||||
|
|
@ -2823,7 +2832,9 @@ async fn test_git_diff_base_change(
|
|||
|
||||
// Create remote buffer
|
||||
let remote_buffer_b = project_remote
|
||||
.update(cx_b, |p, cx| p.open_buffer((worktree_id, "sub/b.txt"), cx))
|
||||
.update(cx_b, |p, cx| {
|
||||
p.open_buffer((worktree_id, rel_path("sub/b.txt")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
let remote_unstaged_diff_b = project_remote
|
||||
|
|
@ -2851,7 +2862,7 @@ async fn test_git_diff_base_change(
|
|||
// Updatet the staged text
|
||||
client_a.fs().set_index_for_repo(
|
||||
Path::new("/dir/sub/.git"),
|
||||
&[("b.txt".into(), new_staged_text.clone())],
|
||||
&[("b.txt", new_staged_text.clone())],
|
||||
);
|
||||
|
||||
// Wait for buffer_local_b to receive it
|
||||
|
|
@ -3011,21 +3022,21 @@ async fn test_git_status_sync(
|
|||
// and b.txt is unmerged.
|
||||
client_a.fs().set_head_for_repo(
|
||||
path!("/dir/.git").as_ref(),
|
||||
&[("b.txt".into(), "B".into()), ("c.txt".into(), "c".into())],
|
||||
&[("b.txt", "B".into()), ("c.txt", "c".into())],
|
||||
"deadbeef",
|
||||
);
|
||||
client_a.fs().set_index_for_repo(
|
||||
path!("/dir/.git").as_ref(),
|
||||
&[
|
||||
("a.txt".into(), "".into()),
|
||||
("b.txt".into(), "B".into()),
|
||||
("c.txt".into(), "c".into()),
|
||||
("a.txt", "".into()),
|
||||
("b.txt", "B".into()),
|
||||
("c.txt", "c".into()),
|
||||
],
|
||||
);
|
||||
client_a.fs().set_unmerged_paths_for_repo(
|
||||
path!("/dir/.git").as_ref(),
|
||||
&[(
|
||||
"b.txt".into(),
|
||||
repo_path("b.txt"),
|
||||
UnmergedStatus {
|
||||
first_head: UnmergedStatusCode::Updated,
|
||||
second_head: UnmergedStatusCode::Deleted,
|
||||
|
|
@ -3056,13 +3067,8 @@ async fn test_git_status_sync(
|
|||
executor.run_until_parked();
|
||||
|
||||
#[track_caller]
|
||||
fn assert_status(
|
||||
file: impl AsRef<Path>,
|
||||
status: Option<FileStatus>,
|
||||
project: &Project,
|
||||
cx: &App,
|
||||
) {
|
||||
let file = file.as_ref();
|
||||
fn assert_status(file: &str, status: Option<FileStatus>, project: &Project, cx: &App) {
|
||||
let file = repo_path(file);
|
||||
let repos = project
|
||||
.repositories(cx)
|
||||
.values()
|
||||
|
|
@ -3072,7 +3078,7 @@ async fn test_git_status_sync(
|
|||
let repo = repos.into_iter().next().unwrap();
|
||||
assert_eq!(
|
||||
repo.read(cx)
|
||||
.status_for_path(&file.into())
|
||||
.status_for_path(&file)
|
||||
.map(|entry| entry.status),
|
||||
status
|
||||
);
|
||||
|
|
@ -3107,7 +3113,7 @@ async fn test_git_status_sync(
|
|||
// and modify c.txt in the working copy.
|
||||
client_a.fs().set_index_for_repo(
|
||||
path!("/dir/.git").as_ref(),
|
||||
&[("a.txt".into(), "a".into()), ("c.txt".into(), "c".into())],
|
||||
&[("a.txt", "a".into()), ("c.txt", "c".into())],
|
||||
);
|
||||
client_a
|
||||
.fs()
|
||||
|
|
@ -3202,7 +3208,7 @@ async fn test_fs_operations(
|
|||
|
||||
let entry = project_b
|
||||
.update(cx_b, |project, cx| {
|
||||
project.create_entry((worktree_id, "c.txt"), false, cx)
|
||||
project.create_entry((worktree_id, rel_path("c.txt")), false, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
|
|
@ -3211,27 +3217,21 @@ async fn test_fs_operations(
|
|||
|
||||
worktree_a.read_with(cx_a, |worktree, _| {
|
||||
assert_eq!(
|
||||
worktree
|
||||
.paths()
|
||||
.map(|p| p.to_string_lossy())
|
||||
.collect::<Vec<_>>(),
|
||||
worktree.paths().map(|p| p.as_str()).collect::<Vec<_>>(),
|
||||
["a.txt", "b.txt", "c.txt"]
|
||||
);
|
||||
});
|
||||
|
||||
worktree_b.read_with(cx_b, |worktree, _| {
|
||||
assert_eq!(
|
||||
worktree
|
||||
.paths()
|
||||
.map(|p| p.to_string_lossy())
|
||||
.collect::<Vec<_>>(),
|
||||
worktree.paths().map(|p| p.as_str()).collect::<Vec<_>>(),
|
||||
["a.txt", "b.txt", "c.txt"]
|
||||
);
|
||||
});
|
||||
|
||||
project_b
|
||||
.update(cx_b, |project, cx| {
|
||||
project.rename_entry(entry.id, Path::new("d.txt"), cx)
|
||||
project.rename_entry(entry.id, (worktree_id, rel_path("d.txt")).into(), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
|
|
@ -3240,27 +3240,21 @@ async fn test_fs_operations(
|
|||
|
||||
worktree_a.read_with(cx_a, |worktree, _| {
|
||||
assert_eq!(
|
||||
worktree
|
||||
.paths()
|
||||
.map(|p| p.to_string_lossy())
|
||||
.collect::<Vec<_>>(),
|
||||
worktree.paths().map(|p| p.as_str()).collect::<Vec<_>>(),
|
||||
["a.txt", "b.txt", "d.txt"]
|
||||
);
|
||||
});
|
||||
|
||||
worktree_b.read_with(cx_b, |worktree, _| {
|
||||
assert_eq!(
|
||||
worktree
|
||||
.paths()
|
||||
.map(|p| p.to_string_lossy())
|
||||
.collect::<Vec<_>>(),
|
||||
worktree.paths().map(|p| p.as_str()).collect::<Vec<_>>(),
|
||||
["a.txt", "b.txt", "d.txt"]
|
||||
);
|
||||
});
|
||||
|
||||
let dir_entry = project_b
|
||||
.update(cx_b, |project, cx| {
|
||||
project.create_entry((worktree_id, "DIR"), true, cx)
|
||||
project.create_entry((worktree_id, rel_path("DIR")), true, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
|
|
@ -3269,27 +3263,21 @@ async fn test_fs_operations(
|
|||
|
||||
worktree_a.read_with(cx_a, |worktree, _| {
|
||||
assert_eq!(
|
||||
worktree
|
||||
.paths()
|
||||
.map(|p| p.to_string_lossy())
|
||||
.collect::<Vec<_>>(),
|
||||
worktree.paths().map(|p| p.as_str()).collect::<Vec<_>>(),
|
||||
["DIR", "a.txt", "b.txt", "d.txt"]
|
||||
);
|
||||
});
|
||||
|
||||
worktree_b.read_with(cx_b, |worktree, _| {
|
||||
assert_eq!(
|
||||
worktree
|
||||
.paths()
|
||||
.map(|p| p.to_string_lossy())
|
||||
.collect::<Vec<_>>(),
|
||||
worktree.paths().map(|p| p.as_str()).collect::<Vec<_>>(),
|
||||
["DIR", "a.txt", "b.txt", "d.txt"]
|
||||
);
|
||||
});
|
||||
|
||||
project_b
|
||||
.update(cx_b, |project, cx| {
|
||||
project.create_entry((worktree_id, "DIR/e.txt"), false, cx)
|
||||
project.create_entry((worktree_id, rel_path("DIR/e.txt")), false, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
|
|
@ -3298,7 +3286,7 @@ async fn test_fs_operations(
|
|||
|
||||
project_b
|
||||
.update(cx_b, |project, cx| {
|
||||
project.create_entry((worktree_id, "DIR/SUBDIR"), true, cx)
|
||||
project.create_entry((worktree_id, rel_path("DIR/SUBDIR")), true, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
|
|
@ -3307,7 +3295,7 @@ async fn test_fs_operations(
|
|||
|
||||
project_b
|
||||
.update(cx_b, |project, cx| {
|
||||
project.create_entry((worktree_id, "DIR/SUBDIR/f.txt"), false, cx)
|
||||
project.create_entry((worktree_id, rel_path("DIR/SUBDIR/f.txt")), false, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
|
|
@ -3316,43 +3304,41 @@ async fn test_fs_operations(
|
|||
|
||||
worktree_a.read_with(cx_a, |worktree, _| {
|
||||
assert_eq!(
|
||||
worktree
|
||||
.paths()
|
||||
.map(|p| p.to_string_lossy())
|
||||
.collect::<Vec<_>>(),
|
||||
worktree.paths().collect::<Vec<_>>(),
|
||||
[
|
||||
path!("DIR"),
|
||||
path!("DIR/SUBDIR"),
|
||||
path!("DIR/SUBDIR/f.txt"),
|
||||
path!("DIR/e.txt"),
|
||||
path!("a.txt"),
|
||||
path!("b.txt"),
|
||||
path!("d.txt")
|
||||
rel_path("DIR"),
|
||||
rel_path("DIR/SUBDIR"),
|
||||
rel_path("DIR/SUBDIR/f.txt"),
|
||||
rel_path("DIR/e.txt"),
|
||||
rel_path("a.txt"),
|
||||
rel_path("b.txt"),
|
||||
rel_path("d.txt")
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
worktree_b.read_with(cx_b, |worktree, _| {
|
||||
assert_eq!(
|
||||
worktree
|
||||
.paths()
|
||||
.map(|p| p.to_string_lossy())
|
||||
.collect::<Vec<_>>(),
|
||||
worktree.paths().collect::<Vec<_>>(),
|
||||
[
|
||||
path!("DIR"),
|
||||
path!("DIR/SUBDIR"),
|
||||
path!("DIR/SUBDIR/f.txt"),
|
||||
path!("DIR/e.txt"),
|
||||
path!("a.txt"),
|
||||
path!("b.txt"),
|
||||
path!("d.txt")
|
||||
rel_path("DIR"),
|
||||
rel_path("DIR/SUBDIR"),
|
||||
rel_path("DIR/SUBDIR/f.txt"),
|
||||
rel_path("DIR/e.txt"),
|
||||
rel_path("a.txt"),
|
||||
rel_path("b.txt"),
|
||||
rel_path("d.txt")
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
project_b
|
||||
.update(cx_b, |project, cx| {
|
||||
project.copy_entry(entry.id, None, Path::new("f.txt"), cx)
|
||||
project.copy_entry(
|
||||
entry.id,
|
||||
(worktree_b.read(cx).id(), rel_path("f.txt")).into(),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
|
|
@ -3360,38 +3346,32 @@ async fn test_fs_operations(
|
|||
|
||||
worktree_a.read_with(cx_a, |worktree, _| {
|
||||
assert_eq!(
|
||||
worktree
|
||||
.paths()
|
||||
.map(|p| p.to_string_lossy())
|
||||
.collect::<Vec<_>>(),
|
||||
worktree.paths().collect::<Vec<_>>(),
|
||||
[
|
||||
path!("DIR"),
|
||||
path!("DIR/SUBDIR"),
|
||||
path!("DIR/SUBDIR/f.txt"),
|
||||
path!("DIR/e.txt"),
|
||||
path!("a.txt"),
|
||||
path!("b.txt"),
|
||||
path!("d.txt"),
|
||||
path!("f.txt")
|
||||
rel_path("DIR"),
|
||||
rel_path("DIR/SUBDIR"),
|
||||
rel_path("DIR/SUBDIR/f.txt"),
|
||||
rel_path("DIR/e.txt"),
|
||||
rel_path("a.txt"),
|
||||
rel_path("b.txt"),
|
||||
rel_path("d.txt"),
|
||||
rel_path("f.txt")
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
worktree_b.read_with(cx_b, |worktree, _| {
|
||||
assert_eq!(
|
||||
worktree
|
||||
.paths()
|
||||
.map(|p| p.to_string_lossy())
|
||||
.collect::<Vec<_>>(),
|
||||
worktree.paths().collect::<Vec<_>>(),
|
||||
[
|
||||
path!("DIR"),
|
||||
path!("DIR/SUBDIR"),
|
||||
path!("DIR/SUBDIR/f.txt"),
|
||||
path!("DIR/e.txt"),
|
||||
path!("a.txt"),
|
||||
path!("b.txt"),
|
||||
path!("d.txt"),
|
||||
path!("f.txt")
|
||||
rel_path("DIR"),
|
||||
rel_path("DIR/SUBDIR"),
|
||||
rel_path("DIR/SUBDIR/f.txt"),
|
||||
rel_path("DIR/e.txt"),
|
||||
rel_path("a.txt"),
|
||||
rel_path("b.txt"),
|
||||
rel_path("d.txt"),
|
||||
rel_path("f.txt")
|
||||
]
|
||||
);
|
||||
});
|
||||
|
|
@ -3406,20 +3386,14 @@ async fn test_fs_operations(
|
|||
|
||||
worktree_a.read_with(cx_a, |worktree, _| {
|
||||
assert_eq!(
|
||||
worktree
|
||||
.paths()
|
||||
.map(|p| p.to_string_lossy())
|
||||
.collect::<Vec<_>>(),
|
||||
worktree.paths().map(|p| p.as_str()).collect::<Vec<_>>(),
|
||||
["a.txt", "b.txt", "d.txt", "f.txt"]
|
||||
);
|
||||
});
|
||||
|
||||
worktree_b.read_with(cx_b, |worktree, _| {
|
||||
assert_eq!(
|
||||
worktree
|
||||
.paths()
|
||||
.map(|p| p.to_string_lossy())
|
||||
.collect::<Vec<_>>(),
|
||||
worktree.paths().map(|p| p.as_str()).collect::<Vec<_>>(),
|
||||
["a.txt", "b.txt", "d.txt", "f.txt"]
|
||||
);
|
||||
});
|
||||
|
|
@ -3433,20 +3407,14 @@ async fn test_fs_operations(
|
|||
|
||||
worktree_a.read_with(cx_a, |worktree, _| {
|
||||
assert_eq!(
|
||||
worktree
|
||||
.paths()
|
||||
.map(|p| p.to_string_lossy())
|
||||
.collect::<Vec<_>>(),
|
||||
worktree.paths().map(|p| p.as_str()).collect::<Vec<_>>(),
|
||||
["a.txt", "b.txt", "f.txt"]
|
||||
);
|
||||
});
|
||||
|
||||
worktree_b.read_with(cx_b, |worktree, _| {
|
||||
assert_eq!(
|
||||
worktree
|
||||
.paths()
|
||||
.map(|p| p.to_string_lossy())
|
||||
.collect::<Vec<_>>(),
|
||||
worktree.paths().map(|p| p.as_str()).collect::<Vec<_>>(),
|
||||
["a.txt", "b.txt", "f.txt"]
|
||||
);
|
||||
});
|
||||
|
|
@ -3511,8 +3479,8 @@ async fn test_local_settings(
|
|||
))
|
||||
.collect::<Vec<_>>(),
|
||||
&[
|
||||
(Path::new("").into(), Some(2)),
|
||||
(Path::new("a").into(), Some(8)),
|
||||
(rel_path("").into(), Some(2)),
|
||||
(rel_path("a").into(), Some(8)),
|
||||
]
|
||||
)
|
||||
});
|
||||
|
|
@ -3533,10 +3501,7 @@ async fn test_local_settings(
|
|||
content.all_languages.defaults.tab_size.map(Into::into)
|
||||
))
|
||||
.collect::<Vec<_>>(),
|
||||
&[
|
||||
(Path::new("").into(), None),
|
||||
(Path::new("a").into(), Some(8)),
|
||||
]
|
||||
&[(rel_path("").into(), None), (rel_path("a").into(), Some(8)),]
|
||||
)
|
||||
});
|
||||
|
||||
|
|
@ -3567,8 +3532,8 @@ async fn test_local_settings(
|
|||
))
|
||||
.collect::<Vec<_>>(),
|
||||
&[
|
||||
(Path::new("a").into(), Some(8)),
|
||||
(Path::new("b").into(), Some(4)),
|
||||
(rel_path("a").into(), Some(8)),
|
||||
(rel_path("b").into(), Some(4)),
|
||||
]
|
||||
)
|
||||
});
|
||||
|
|
@ -3599,7 +3564,7 @@ async fn test_local_settings(
|
|||
.local_settings(worktree_b.read(cx).id())
|
||||
.map(|(path, content)| (path, content.all_languages.defaults.hard_tabs))
|
||||
.collect::<Vec<_>>(),
|
||||
&[(Path::new("a").into(), Some(true))],
|
||||
&[(rel_path("a").into(), Some(true))],
|
||||
)
|
||||
});
|
||||
}
|
||||
|
|
@ -3636,7 +3601,9 @@ async fn test_buffer_conflict_after_save(
|
|||
|
||||
// Open a buffer as client B
|
||||
let buffer_b = project_b
|
||||
.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
|
||||
.update(cx_b, |p, cx| {
|
||||
p.open_buffer((worktree_id, rel_path("a.txt")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
|
@ -3700,7 +3667,9 @@ async fn test_buffer_reloading(
|
|||
|
||||
// Open a buffer as client B
|
||||
let buffer_b = project_b
|
||||
.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
|
||||
.update(cx_b, |p, cx| {
|
||||
p.open_buffer((worktree_id, rel_path("a.txt")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
|
@ -3758,12 +3727,16 @@ async fn test_editing_while_guest_opens_buffer(
|
|||
|
||||
// Open a buffer as client A
|
||||
let buffer_a = project_a
|
||||
.update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
|
||||
.update(cx_a, |p, cx| {
|
||||
p.open_buffer((worktree_id, rel_path("a.txt")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Start opening the same buffer as client B
|
||||
let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx));
|
||||
let open_buffer = project_b.update(cx_b, |p, cx| {
|
||||
p.open_buffer((worktree_id, rel_path("a.txt")), cx)
|
||||
});
|
||||
let buffer_b = cx_b.executor().spawn(open_buffer);
|
||||
|
||||
// Edit the buffer as client A while client B is still opening it.
|
||||
|
|
@ -3810,7 +3783,9 @@ async fn test_leaving_worktree_while_opening_buffer(
|
|||
project_a.read_with(cx_a, |p, _| assert_eq!(p.collaborators().len(), 1));
|
||||
|
||||
// Begin opening a buffer as client B, but leave the project before the open completes.
|
||||
let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx));
|
||||
let open_buffer = project_b.update(cx_b, |p, cx| {
|
||||
p.open_buffer((worktree_id, rel_path("a.txt")), cx)
|
||||
});
|
||||
let buffer_b = cx_b.executor().spawn(open_buffer);
|
||||
cx_b.update(|_| drop(project_b));
|
||||
drop(buffer_b);
|
||||
|
|
@ -3852,7 +3827,9 @@ async fn test_canceling_buffer_opening(
|
|||
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
||||
|
||||
let buffer_a = project_a
|
||||
.update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
|
||||
.update(cx_a, |p, cx| {
|
||||
p.open_buffer((worktree_id, rel_path("a.txt")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
|
@ -3928,7 +3905,7 @@ async fn test_leaving_project(
|
|||
let buffer_b1 = project_b1
|
||||
.update(cx_b, |project, cx| {
|
||||
let worktree_id = project.worktrees(cx).next().unwrap().read(cx).id();
|
||||
project.open_buffer((worktree_id, "a.txt"), cx)
|
||||
project.open_buffer((worktree_id, rel_path("a.txt")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
|
@ -3966,7 +3943,7 @@ async fn test_leaving_project(
|
|||
let buffer_b2 = project_b2
|
||||
.update(cx_b, |project, cx| {
|
||||
let worktree_id = project.worktrees(cx).next().unwrap().read(cx).id();
|
||||
project.open_buffer((worktree_id, "a.txt"), cx)
|
||||
project.open_buffer((worktree_id, rel_path("a.txt")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
|
@ -4131,7 +4108,7 @@ async fn test_collaborating_with_diagnostics(
|
|||
&[(
|
||||
ProjectPath {
|
||||
worktree_id,
|
||||
path: Arc::from(Path::new("a.rs")),
|
||||
path: rel_path("a.rs").into(),
|
||||
},
|
||||
LanguageServerId(0),
|
||||
DiagnosticSummary {
|
||||
|
|
@ -4167,7 +4144,7 @@ async fn test_collaborating_with_diagnostics(
|
|||
&[(
|
||||
ProjectPath {
|
||||
worktree_id,
|
||||
path: Arc::from(Path::new("a.rs")),
|
||||
path: rel_path("a.rs").into(),
|
||||
},
|
||||
LanguageServerId(0),
|
||||
DiagnosticSummary {
|
||||
|
|
@ -4208,7 +4185,7 @@ async fn test_collaborating_with_diagnostics(
|
|||
[(
|
||||
ProjectPath {
|
||||
worktree_id,
|
||||
path: Arc::from(Path::new("a.rs")),
|
||||
path: rel_path("a.rs").into(),
|
||||
},
|
||||
LanguageServerId(0),
|
||||
DiagnosticSummary {
|
||||
|
|
@ -4225,7 +4202,7 @@ async fn test_collaborating_with_diagnostics(
|
|||
[(
|
||||
ProjectPath {
|
||||
worktree_id,
|
||||
path: Arc::from(Path::new("a.rs")),
|
||||
path: rel_path("a.rs").into(),
|
||||
},
|
||||
LanguageServerId(0),
|
||||
DiagnosticSummary {
|
||||
|
|
@ -4237,7 +4214,9 @@ async fn test_collaborating_with_diagnostics(
|
|||
});
|
||||
|
||||
// Open the file with the errors on client B. They should be present.
|
||||
let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx));
|
||||
let open_buffer = project_b.update(cx_b, |p, cx| {
|
||||
p.open_buffer((worktree_id, rel_path("a.rs")), cx)
|
||||
});
|
||||
let buffer_b = cx_b.executor().spawn(open_buffer).await.unwrap();
|
||||
|
||||
buffer_b.read_with(cx_b, |buffer, _| {
|
||||
|
|
@ -4356,7 +4335,7 @@ async fn test_collaborating_with_lsp_progress_updates_and_diagnostics_ordering(
|
|||
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
||||
let guest_buffers = futures::future::try_join_all(file_names.iter().map(|file_name| {
|
||||
project_b.update(cx_b, |p, cx| {
|
||||
p.open_buffer_with_lsp((worktree_id, file_name), cx)
|
||||
p.open_buffer_with_lsp((worktree_id, rel_path(file_name)), cx)
|
||||
})
|
||||
}))
|
||||
.await
|
||||
|
|
@ -4454,7 +4433,9 @@ async fn test_reloading_buffer_manually(
|
|||
.await;
|
||||
let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
|
||||
let buffer_a = project_a
|
||||
.update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx))
|
||||
.update(cx_a, |p, cx| {
|
||||
p.open_buffer((worktree_id, rel_path("a.rs")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
let project_id = active_call_a
|
||||
|
|
@ -4464,7 +4445,9 @@ async fn test_reloading_buffer_manually(
|
|||
|
||||
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
||||
|
||||
let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx));
|
||||
let open_buffer = project_b.update(cx_b, |p, cx| {
|
||||
p.open_buffer((worktree_id, rel_path("a.rs")), cx)
|
||||
});
|
||||
let buffer_b = cx_b.executor().spawn(open_buffer).await.unwrap();
|
||||
buffer_b.update(cx_b, |buffer, cx| {
|
||||
buffer.edit([(4..7, "six")], None, cx);
|
||||
|
|
@ -4562,7 +4545,9 @@ async fn test_formatting_buffer(
|
|||
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
||||
|
||||
let buffer_b = project_b
|
||||
.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx))
|
||||
.update(cx_b, |p, cx| {
|
||||
p.open_buffer((worktree_id, rel_path("a.rs")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
|
@ -4688,7 +4673,9 @@ async fn test_prettier_formatting_buffer(
|
|||
.await;
|
||||
let (project_a, worktree_id) = client_a.build_local_project(&directory, cx_a).await;
|
||||
let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
|
||||
let open_buffer = project_a.update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.ts"), cx));
|
||||
let open_buffer = project_a.update(cx_a, |p, cx| {
|
||||
p.open_buffer((worktree_id, rel_path("a.ts")), cx)
|
||||
});
|
||||
let buffer_a = cx_a.executor().spawn(open_buffer).await.unwrap();
|
||||
|
||||
let project_id = active_call_a
|
||||
|
|
@ -4698,7 +4685,7 @@ async fn test_prettier_formatting_buffer(
|
|||
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
||||
let (buffer_b, _) = project_b
|
||||
.update(cx_b, |p, cx| {
|
||||
p.open_buffer_with_lsp((worktree_id, "a.ts"), cx)
|
||||
p.open_buffer_with_lsp((worktree_id, rel_path("a.ts")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
|
@ -4838,7 +4825,7 @@ async fn test_definition(
|
|||
// Open the file on client B.
|
||||
let (buffer_b, _handle) = project_b
|
||||
.update(cx_b, |p, cx| {
|
||||
p.open_buffer_with_lsp((worktree_id, "a.rs"), cx)
|
||||
p.open_buffer_with_lsp((worktree_id, rel_path("a.rs")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
|
@ -5016,7 +5003,7 @@ async fn test_references(
|
|||
// Open the file on client B.
|
||||
let (buffer_b, _handle) = project_b
|
||||
.update(cx_b, |p, cx| {
|
||||
p.open_buffer_with_lsp((worktree_id, "one.rs"), cx)
|
||||
p.open_buffer_with_lsp((worktree_id, rel_path("one.rs")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
|
@ -5088,7 +5075,7 @@ async fn test_references(
|
|||
let three_buffer = references[2].buffer.read(cx);
|
||||
assert_eq!(
|
||||
two_buffer.file().unwrap().path().as_ref(),
|
||||
Path::new("two.rs")
|
||||
rel_path("two.rs")
|
||||
);
|
||||
assert_eq!(references[1].buffer, references[0].buffer);
|
||||
assert_eq!(
|
||||
|
|
@ -5288,7 +5275,7 @@ async fn test_document_highlights(
|
|||
// Open the file on client B.
|
||||
let (buffer_b, _handle) = project_b
|
||||
.update(cx_b, |p, cx| {
|
||||
p.open_buffer_with_lsp((worktree_id, "main.rs"), cx)
|
||||
p.open_buffer_with_lsp((worktree_id, rel_path("main.rs")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
|
@ -5431,7 +5418,7 @@ async fn test_lsp_hover(
|
|||
// Open the file as the guest
|
||||
let (buffer_b, _handle) = project_b
|
||||
.update(cx_b, |p, cx| {
|
||||
p.open_buffer_with_lsp((worktree_id, "main.rs"), cx)
|
||||
p.open_buffer_with_lsp((worktree_id, rel_path("main.rs")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
|
@ -5623,7 +5610,7 @@ async fn test_project_symbols(
|
|||
// Cause the language server to start.
|
||||
let _buffer = project_b
|
||||
.update(cx_b, |p, cx| {
|
||||
p.open_buffer_with_lsp((worktree_id, "one.rs"), cx)
|
||||
p.open_buffer_with_lsp((worktree_id, rel_path("one.rs")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
|
@ -5673,7 +5660,10 @@ async fn test_project_symbols(
|
|||
|
||||
// Attempt to craft a symbol and violate host's privacy by opening an arbitrary file.
|
||||
let mut fake_symbol = symbols[0].clone();
|
||||
fake_symbol.path.path = Path::new(path!("/code/secrets")).into();
|
||||
fake_symbol.path = SymbolLocation::OutsideProject {
|
||||
abs_path: Path::new(path!("/code/secrets")).into(),
|
||||
signature: [0x17; 32],
|
||||
};
|
||||
let error = project_b
|
||||
.update(cx_b, |project, cx| {
|
||||
project.open_buffer_for_symbol(&fake_symbol, cx)
|
||||
|
|
@ -5738,7 +5728,7 @@ async fn test_open_buffer_while_getting_definition_pointing_to_it(
|
|||
|
||||
let (buffer_b1, _lsp) = project_b
|
||||
.update(cx_b, |p, cx| {
|
||||
p.open_buffer_with_lsp((worktree_id, "a.rs"), cx)
|
||||
p.open_buffer_with_lsp((worktree_id, rel_path("a.rs")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
|
@ -5763,14 +5753,14 @@ async fn test_open_buffer_while_getting_definition_pointing_to_it(
|
|||
definitions = project_b.update(cx_b, |p, cx| p.definitions(&buffer_b1, 23, cx));
|
||||
(buffer_b2, _) = project_b
|
||||
.update(cx_b, |p, cx| {
|
||||
p.open_buffer_with_lsp((worktree_id, "b.rs"), cx)
|
||||
p.open_buffer_with_lsp((worktree_id, rel_path("b.rs")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
} else {
|
||||
(buffer_b2, _) = project_b
|
||||
.update(cx_b, |p, cx| {
|
||||
p.open_buffer_with_lsp((worktree_id, "b.rs"), cx)
|
||||
p.open_buffer_with_lsp((worktree_id, rel_path("b.rs")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
|
@ -6587,15 +6577,15 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
|
|||
|
||||
let path_1 = ProjectPath {
|
||||
worktree_id,
|
||||
path: Path::new("1.txt").into(),
|
||||
path: rel_path("1.txt").into(),
|
||||
};
|
||||
let path_2 = ProjectPath {
|
||||
worktree_id,
|
||||
path: Path::new("2.js").into(),
|
||||
path: rel_path("2.js").into(),
|
||||
};
|
||||
let path_3 = ProjectPath {
|
||||
worktree_id,
|
||||
path: Path::new("3.rs").into(),
|
||||
path: rel_path("3.rs").into(),
|
||||
};
|
||||
|
||||
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
|
||||
|
|
|
|||
|
|
@ -27,7 +27,11 @@ use std::{
|
|||
rc::Rc,
|
||||
sync::Arc,
|
||||
};
|
||||
use util::{ResultExt, path};
|
||||
use util::{
|
||||
ResultExt, path,
|
||||
paths::PathStyle,
|
||||
rel_path::{RelPath, RelPathBuf, rel_path},
|
||||
};
|
||||
|
||||
#[gpui::test(
|
||||
iterations = 100,
|
||||
|
|
@ -66,7 +70,7 @@ enum ClientOperation {
|
|||
OpenBuffer {
|
||||
project_root_name: String,
|
||||
is_local: bool,
|
||||
full_path: PathBuf,
|
||||
full_path: RelPathBuf,
|
||||
},
|
||||
SearchProject {
|
||||
project_root_name: String,
|
||||
|
|
@ -77,24 +81,24 @@ enum ClientOperation {
|
|||
EditBuffer {
|
||||
project_root_name: String,
|
||||
is_local: bool,
|
||||
full_path: PathBuf,
|
||||
full_path: RelPathBuf,
|
||||
edits: Vec<(Range<usize>, Arc<str>)>,
|
||||
},
|
||||
CloseBuffer {
|
||||
project_root_name: String,
|
||||
is_local: bool,
|
||||
full_path: PathBuf,
|
||||
full_path: RelPathBuf,
|
||||
},
|
||||
SaveBuffer {
|
||||
project_root_name: String,
|
||||
is_local: bool,
|
||||
full_path: PathBuf,
|
||||
full_path: RelPathBuf,
|
||||
detach: bool,
|
||||
},
|
||||
RequestLspDataInBuffer {
|
||||
project_root_name: String,
|
||||
is_local: bool,
|
||||
full_path: PathBuf,
|
||||
full_path: RelPathBuf,
|
||||
offset: usize,
|
||||
kind: LspRequestKind,
|
||||
detach: bool,
|
||||
|
|
@ -102,7 +106,7 @@ enum ClientOperation {
|
|||
CreateWorktreeEntry {
|
||||
project_root_name: String,
|
||||
is_local: bool,
|
||||
full_path: PathBuf,
|
||||
full_path: RelPathBuf,
|
||||
is_dir: bool,
|
||||
},
|
||||
WriteFsEntry {
|
||||
|
|
@ -119,7 +123,7 @@ enum ClientOperation {
|
|||
enum GitOperation {
|
||||
WriteGitIndex {
|
||||
repo_path: PathBuf,
|
||||
contents: Vec<(PathBuf, String)>,
|
||||
contents: Vec<(RelPathBuf, String)>,
|
||||
},
|
||||
WriteGitBranch {
|
||||
repo_path: PathBuf,
|
||||
|
|
@ -127,7 +131,7 @@ enum GitOperation {
|
|||
},
|
||||
WriteGitStatuses {
|
||||
repo_path: PathBuf,
|
||||
statuses: Vec<(PathBuf, FileStatus)>,
|
||||
statuses: Vec<(RelPathBuf, FileStatus)>,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -311,8 +315,8 @@ impl RandomizedTest for ProjectCollaborationTest {
|
|||
let Some(worktree) = worktree else { continue };
|
||||
let is_dir = rng.random::<bool>();
|
||||
let mut full_path =
|
||||
worktree.read_with(cx, |w, _| PathBuf::from(w.root_name()));
|
||||
full_path.push(gen_file_name(rng));
|
||||
worktree.read_with(cx, |w, _| w.root_name().to_rel_path_buf());
|
||||
full_path.push(rel_path(&gen_file_name(rng)));
|
||||
if !is_dir {
|
||||
full_path.set_extension("rs");
|
||||
}
|
||||
|
|
@ -346,8 +350,18 @@ impl RandomizedTest for ProjectCollaborationTest {
|
|||
continue;
|
||||
};
|
||||
|
||||
let full_path = buffer
|
||||
.read_with(cx, |buffer, cx| buffer.file().unwrap().full_path(cx));
|
||||
let full_path = buffer.read_with(cx, |buffer, cx| {
|
||||
let file = buffer.file().unwrap();
|
||||
let worktree = project
|
||||
.read(cx)
|
||||
.worktree_for_id(file.worktree_id(cx), cx)
|
||||
.unwrap();
|
||||
worktree
|
||||
.read(cx)
|
||||
.root_name()
|
||||
.join(file.path())
|
||||
.to_rel_path_buf()
|
||||
});
|
||||
|
||||
match rng.random_range(0..100_u32) {
|
||||
// Close the buffer
|
||||
|
|
@ -436,16 +450,16 @@ impl RandomizedTest for ProjectCollaborationTest {
|
|||
.filter(|e| e.is_file())
|
||||
.choose(rng)
|
||||
.unwrap();
|
||||
if entry.path.as_ref() == Path::new("") {
|
||||
Path::new(worktree.root_name()).into()
|
||||
if entry.path.as_ref().is_empty() {
|
||||
worktree.root_name().into()
|
||||
} else {
|
||||
Path::new(worktree.root_name()).join(&entry.path)
|
||||
worktree.root_name().join(&entry.path)
|
||||
}
|
||||
});
|
||||
break ClientOperation::OpenBuffer {
|
||||
project_root_name,
|
||||
is_local,
|
||||
full_path,
|
||||
full_path: full_path.to_rel_path_buf(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -940,7 +954,11 @@ impl RandomizedTest for ProjectCollaborationTest {
|
|||
}
|
||||
|
||||
for (path, _) in contents.iter() {
|
||||
if !client.fs().files().contains(&repo_path.join(path)) {
|
||||
if !client
|
||||
.fs()
|
||||
.files()
|
||||
.contains(&repo_path.join(path.as_std_path()))
|
||||
{
|
||||
return Err(TestError::Inapplicable);
|
||||
}
|
||||
}
|
||||
|
|
@ -954,8 +972,8 @@ impl RandomizedTest for ProjectCollaborationTest {
|
|||
|
||||
let dot_git_dir = repo_path.join(".git");
|
||||
let contents = contents
|
||||
.into_iter()
|
||||
.map(|(path, contents)| (path.into(), contents))
|
||||
.iter()
|
||||
.map(|(path, contents)| (path.as_str(), contents.clone()))
|
||||
.collect::<Vec<_>>();
|
||||
if client.fs().metadata(&dot_git_dir).await?.is_none() {
|
||||
client.fs().create_dir(&dot_git_dir).await?;
|
||||
|
|
@ -993,7 +1011,11 @@ impl RandomizedTest for ProjectCollaborationTest {
|
|||
return Err(TestError::Inapplicable);
|
||||
}
|
||||
for (path, _) in statuses.iter() {
|
||||
if !client.fs().files().contains(&repo_path.join(path)) {
|
||||
if !client
|
||||
.fs()
|
||||
.files()
|
||||
.contains(&repo_path.join(path.as_std_path()))
|
||||
{
|
||||
return Err(TestError::Inapplicable);
|
||||
}
|
||||
}
|
||||
|
|
@ -1009,7 +1031,7 @@ impl RandomizedTest for ProjectCollaborationTest {
|
|||
|
||||
let statuses = statuses
|
||||
.iter()
|
||||
.map(|(path, val)| (path.as_path(), *val))
|
||||
.map(|(path, val)| (path.as_str(), *val))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if client.fs().metadata(&dot_git_dir).await?.is_none() {
|
||||
|
|
@ -1426,7 +1448,7 @@ fn generate_git_operation(rng: &mut StdRng, client: &TestClient) -> GitOperation
|
|||
repo_path: &Path,
|
||||
rng: &mut StdRng,
|
||||
client: &TestClient,
|
||||
) -> Vec<PathBuf> {
|
||||
) -> Vec<RelPathBuf> {
|
||||
let mut paths = client
|
||||
.fs()
|
||||
.files()
|
||||
|
|
@ -1440,7 +1462,11 @@ fn generate_git_operation(rng: &mut StdRng, client: &TestClient) -> GitOperation
|
|||
|
||||
paths
|
||||
.iter()
|
||||
.map(|path| path.strip_prefix(repo_path).unwrap().to_path_buf())
|
||||
.map(|path| {
|
||||
RelPath::from_std_path(path.strip_prefix(repo_path).unwrap(), PathStyle::local())
|
||||
.unwrap()
|
||||
.to_rel_path_buf()
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
|
|
@ -1487,7 +1513,7 @@ fn generate_git_operation(rng: &mut StdRng, client: &TestClient) -> GitOperation
|
|||
fn buffer_for_full_path(
|
||||
client: &TestClient,
|
||||
project: &Entity<Project>,
|
||||
full_path: &PathBuf,
|
||||
full_path: &RelPath,
|
||||
cx: &TestAppContext,
|
||||
) -> Option<Entity<language::Buffer>> {
|
||||
client
|
||||
|
|
@ -1495,7 +1521,12 @@ fn buffer_for_full_path(
|
|||
.iter()
|
||||
.find(|buffer| {
|
||||
buffer.read_with(cx, |buffer, cx| {
|
||||
buffer.file().unwrap().full_path(cx) == *full_path
|
||||
let file = buffer.file().unwrap();
|
||||
let Some(worktree) = project.read(cx).worktree_for_id(file.worktree_id(cx), cx)
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
worktree.read(cx).root_name().join(&file.path()).as_ref() == full_path
|
||||
})
|
||||
})
|
||||
.cloned()
|
||||
|
|
@ -1536,23 +1567,23 @@ fn root_name_for_project(project: &Entity<Project>, cx: &TestAppContext) -> Stri
|
|||
.next()
|
||||
.unwrap()
|
||||
.read(cx)
|
||||
.root_name()
|
||||
.root_name_str()
|
||||
.to_string()
|
||||
})
|
||||
}
|
||||
|
||||
fn project_path_for_full_path(
|
||||
project: &Entity<Project>,
|
||||
full_path: &Path,
|
||||
full_path: &RelPath,
|
||||
cx: &TestAppContext,
|
||||
) -> Option<ProjectPath> {
|
||||
let mut components = full_path.components();
|
||||
let root_name = components.next().unwrap().as_os_str().to_str().unwrap();
|
||||
let path = components.as_path().into();
|
||||
let root_name = components.next().unwrap();
|
||||
let path = components.rest().into();
|
||||
let worktree_id = project.read_with(cx, |project, cx| {
|
||||
project.worktrees(cx).find_map(|worktree| {
|
||||
let worktree = worktree.read(cx);
|
||||
if worktree.root_name() == root_name {
|
||||
if worktree.root_name_str() == root_name {
|
||||
Some(worktree.id())
|
||||
} else {
|
||||
None
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ use std::{
|
|||
sync::{Arc, atomic::AtomicUsize},
|
||||
};
|
||||
use task::TcpArgumentsTemplate;
|
||||
use util::path;
|
||||
use util::{path, rel_path::rel_path};
|
||||
|
||||
#[gpui::test(iterations = 10)]
|
||||
async fn test_sharing_an_ssh_remote_project(
|
||||
|
|
@ -124,26 +124,26 @@ async fn test_sharing_an_ssh_remote_project(
|
|||
|
||||
worktree_a.update(cx_a, |worktree, _cx| {
|
||||
assert_eq!(
|
||||
worktree.paths().map(Arc::as_ref).collect::<Vec<_>>(),
|
||||
worktree.paths().collect::<Vec<_>>(),
|
||||
vec![
|
||||
Path::new(".zed"),
|
||||
Path::new(".zed/settings.json"),
|
||||
Path::new("README.md"),
|
||||
Path::new("src"),
|
||||
Path::new("src/lib.rs"),
|
||||
rel_path(".zed"),
|
||||
rel_path(".zed/settings.json"),
|
||||
rel_path("README.md"),
|
||||
rel_path("src"),
|
||||
rel_path("src/lib.rs"),
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
worktree_b.update(cx_b, |worktree, _cx| {
|
||||
assert_eq!(
|
||||
worktree.paths().map(Arc::as_ref).collect::<Vec<_>>(),
|
||||
worktree.paths().collect::<Vec<_>>(),
|
||||
vec![
|
||||
Path::new(".zed"),
|
||||
Path::new(".zed/settings.json"),
|
||||
Path::new("README.md"),
|
||||
Path::new("src"),
|
||||
Path::new("src/lib.rs"),
|
||||
rel_path(".zed"),
|
||||
rel_path(".zed/settings.json"),
|
||||
rel_path("README.md"),
|
||||
rel_path("src"),
|
||||
rel_path("src/lib.rs"),
|
||||
]
|
||||
);
|
||||
});
|
||||
|
|
@ -151,7 +151,7 @@ async fn test_sharing_an_ssh_remote_project(
|
|||
// User B can open buffers in the remote project.
|
||||
let buffer_b = project_b
|
||||
.update(cx_b, |project, cx| {
|
||||
project.open_buffer((worktree_id, "src/lib.rs"), cx)
|
||||
project.open_buffer((worktree_id, rel_path("src/lib.rs")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
|
@ -177,7 +177,7 @@ async fn test_sharing_an_ssh_remote_project(
|
|||
buffer_b.clone(),
|
||||
ProjectPath {
|
||||
worktree_id: worktree_id.to_owned(),
|
||||
path: Arc::from(Path::new("src/renamed.rs")),
|
||||
path: rel_path("src/renamed.rs").into(),
|
||||
},
|
||||
cx,
|
||||
)
|
||||
|
|
@ -194,14 +194,8 @@ async fn test_sharing_an_ssh_remote_project(
|
|||
cx_b.run_until_parked();
|
||||
cx_b.update(|cx| {
|
||||
assert_eq!(
|
||||
buffer_b
|
||||
.read(cx)
|
||||
.file()
|
||||
.unwrap()
|
||||
.path()
|
||||
.to_string_lossy()
|
||||
.to_string(),
|
||||
path!("src/renamed.rs").to_string()
|
||||
buffer_b.read(cx).file().unwrap().path().as_ref(),
|
||||
rel_path("src/renamed.rs")
|
||||
);
|
||||
});
|
||||
}
|
||||
|
|
@ -489,7 +483,7 @@ async fn test_ssh_collaboration_formatting_with_prettier(
|
|||
// Opens the buffer and formats it
|
||||
let (buffer_b, _handle) = project_b
|
||||
.update(cx_b, |p, cx| {
|
||||
p.open_buffer_with_lsp((worktree_id, "a.ts"), cx)
|
||||
p.open_buffer_with_lsp((worktree_id, rel_path("a.ts")), cx)
|
||||
})
|
||||
.await
|
||||
.expect("user B opens buffer for formatting");
|
||||
|
|
@ -547,7 +541,9 @@ async fn test_ssh_collaboration_formatting_with_prettier(
|
|||
|
||||
// User A opens and formats the same buffer too
|
||||
let buffer_a = project_a
|
||||
.update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.ts"), cx))
|
||||
.update(cx_a, |p, cx| {
|
||||
p.open_buffer((worktree_id, rel_path("a.ts")), cx)
|
||||
})
|
||||
.await
|
||||
.expect("user A opens buffer for formatting");
|
||||
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ use std::{
|
|||
sync::Arc,
|
||||
};
|
||||
use sum_tree::Dimensions;
|
||||
use util::rel_path::RelPath;
|
||||
use util::{ResultExt, fs::remove_matching};
|
||||
use workspace::Workspace;
|
||||
|
||||
|
|
@ -963,8 +964,7 @@ impl Copilot {
|
|||
let hard_tabs = settings.hard_tabs;
|
||||
let relative_path = buffer
|
||||
.file()
|
||||
.map(|file| file.path().to_path_buf())
|
||||
.unwrap_or_default();
|
||||
.map_or(RelPath::empty().into(), |file| file.path().clone());
|
||||
|
||||
cx.background_spawn(async move {
|
||||
let (version, snapshot) = snapshot.await?;
|
||||
|
|
@ -975,7 +975,7 @@ impl Copilot {
|
|||
tab_size: tab_size.into(),
|
||||
indent_size: 1,
|
||||
insert_spaces: !hard_tabs,
|
||||
relative_path: relative_path.to_string_lossy().into(),
|
||||
relative_path: relative_path.to_proto(),
|
||||
position: point_to_lsp(position),
|
||||
version: version.try_into().unwrap(),
|
||||
},
|
||||
|
|
@ -1194,7 +1194,7 @@ async fn get_copilot_lsp(fs: Arc<dyn Fs>, node_runtime: NodeRuntime) -> anyhow::
|
|||
mod tests {
|
||||
use super::*;
|
||||
use gpui::TestAppContext;
|
||||
use util::path;
|
||||
use util::{path, paths::PathStyle, rel_path::rel_path};
|
||||
|
||||
#[gpui::test(iterations = 10)]
|
||||
async fn test_buffer_management(cx: &mut TestAppContext) {
|
||||
|
|
@ -1258,7 +1258,7 @@ mod tests {
|
|||
buffer.file_updated(
|
||||
Arc::new(File {
|
||||
abs_path: path!("/root/child/buffer-1").into(),
|
||||
path: Path::new("child/buffer-1").into(),
|
||||
path: rel_path("child/buffer-1").into(),
|
||||
}),
|
||||
cx,
|
||||
)
|
||||
|
|
@ -1355,7 +1355,7 @@ mod tests {
|
|||
|
||||
struct File {
|
||||
abs_path: PathBuf,
|
||||
path: Arc<Path>,
|
||||
path: Arc<RelPath>,
|
||||
}
|
||||
|
||||
impl language::File for File {
|
||||
|
|
@ -1369,15 +1369,19 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
fn path(&self) -> &Arc<Path> {
|
||||
fn path(&self) -> &Arc<RelPath> {
|
||||
&self.path
|
||||
}
|
||||
|
||||
fn path_style(&self, _: &App) -> PathStyle {
|
||||
PathStyle::local()
|
||||
}
|
||||
|
||||
fn full_path(&self, _: &App) -> PathBuf {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn file_name<'a>(&'a self, _: &'a App) -> &'a std::ffi::OsStr {
|
||||
fn file_name<'a>(&'a self, _: &'a App) -> &'a str {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ use std::{
|
|||
sync::Arc,
|
||||
};
|
||||
use task::{DebugScenario, TcpArgumentsTemplate, ZedDebugConfig};
|
||||
use util::archive::extract_zip;
|
||||
use util::{archive::extract_zip, rel_path::RelPath};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum DapStatus {
|
||||
|
|
@ -44,7 +44,7 @@ pub trait DapDelegate: Send + Sync + 'static {
|
|||
fn fs(&self) -> Arc<dyn Fs>;
|
||||
fn output_to_console(&self, msg: String);
|
||||
async fn which(&self, command: &OsStr) -> Option<PathBuf>;
|
||||
async fn read_text_file(&self, path: PathBuf) -> Result<String>;
|
||||
async fn read_text_file(&self, path: &RelPath) -> Result<String>;
|
||||
async fn shell_env(&self) -> collections::HashMap<String, String>;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ use std::{
|
|||
ffi::OsStr,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use util::{ResultExt, maybe};
|
||||
use util::{ResultExt, maybe, paths::PathStyle, rel_path::RelPath};
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct PythonDebugAdapter {
|
||||
|
|
@ -726,13 +726,16 @@ impl DebugAdapter for PythonDebugAdapter {
|
|||
.config
|
||||
.get("cwd")
|
||||
.and_then(|cwd| {
|
||||
cwd.as_str()
|
||||
.map(Path::new)?
|
||||
.strip_prefix(delegate.worktree_root_path())
|
||||
.ok()
|
||||
RelPath::from_std_path(
|
||||
cwd.as_str()
|
||||
.map(Path::new)?
|
||||
.strip_prefix(delegate.worktree_root_path())
|
||||
.ok()?,
|
||||
PathStyle::local(),
|
||||
)
|
||||
.ok()
|
||||
})
|
||||
.unwrap_or_else(|| "".as_ref())
|
||||
.into();
|
||||
.unwrap_or_else(|| RelPath::empty().into());
|
||||
let toolchain = delegate
|
||||
.toolchain_store()
|
||||
.active_toolchain(
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ use dap::{
|
|||
use extension::{Extension, WorktreeDelegate};
|
||||
use gpui::AsyncApp;
|
||||
use task::{DebugScenario, ZedDebugConfig};
|
||||
use util::rel_path::RelPath;
|
||||
|
||||
pub(crate) struct ExtensionDapAdapter {
|
||||
extension: Arc<dyn Extension>,
|
||||
|
|
@ -57,7 +58,7 @@ impl WorktreeDelegate for WorktreeDelegateAdapter {
|
|||
self.0.worktree_root_path().to_string_lossy().to_string()
|
||||
}
|
||||
|
||||
async fn read_text_file(&self, path: PathBuf) -> Result<String> {
|
||||
async fn read_text_file(&self, path: &RelPath) -> Result<String> {
|
||||
self.0.read_text_file(path).await
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ use std::sync::{Arc, LazyLock};
|
|||
use task::{DebugScenario, TaskContext};
|
||||
use tree_sitter::{Query, StreamingIterator as _};
|
||||
use ui::{ContextMenu, Divider, PopoverMenuHandle, Tab, Tooltip, prelude::*};
|
||||
use util::rel_path::RelPath;
|
||||
use util::{ResultExt, debug_panic, maybe};
|
||||
use workspace::SplitDirection;
|
||||
use workspace::item::SaveOptions;
|
||||
|
|
@ -1061,14 +1062,14 @@ impl DebugPanel {
|
|||
directory_in_worktree: dir,
|
||||
..
|
||||
} => {
|
||||
let relative_path = if dir.ends_with(".vscode") {
|
||||
dir.join("launch.json")
|
||||
let relative_path = if dir.ends_with(RelPath::new(".vscode").unwrap()) {
|
||||
dir.join(RelPath::new("launch.json").unwrap())
|
||||
} else {
|
||||
dir.join("debug.json")
|
||||
dir.join(RelPath::new("debug.json").unwrap())
|
||||
};
|
||||
ProjectPath {
|
||||
worktree_id: id,
|
||||
path: Arc::from(relative_path),
|
||||
path: relative_path,
|
||||
}
|
||||
}
|
||||
_ => return self.save_scenario(scenario, worktree_id, window, cx),
|
||||
|
|
@ -1129,7 +1130,7 @@ impl DebugPanel {
|
|||
let fs =
|
||||
workspace.read_with(cx, |workspace, _| workspace.app_state().fs.clone())?;
|
||||
|
||||
path.push(paths::local_settings_folder_relative_path());
|
||||
path.push(paths::local_settings_folder_name());
|
||||
if !fs.is_dir(path.as_path()).await {
|
||||
fs.create_dir(path.as_path()).await?;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ use ui::{
|
|||
SharedString, Styled, StyledExt, ToggleButton, ToggleState, Toggleable, Tooltip, Window, div,
|
||||
h_flex, relative, rems, v_flex,
|
||||
};
|
||||
use util::ResultExt;
|
||||
use util::{ResultExt, rel_path::RelPath};
|
||||
use workspace::{ModalView, Workspace, notifications::DetachAndPromptErr, pane};
|
||||
|
||||
use crate::{attach_modal::AttachModal, debugger_panel::DebugPanel};
|
||||
|
|
@ -1026,29 +1026,27 @@ impl DebugDelegate {
|
|||
let mut path = if worktrees.len() > 1
|
||||
&& let Some(worktree) = project.worktree_for_id(*worktree_id, cx)
|
||||
{
|
||||
let worktree_path = worktree.read(cx).abs_path();
|
||||
let full_path = worktree_path.join(directory_in_worktree);
|
||||
full_path
|
||||
worktree
|
||||
.read(cx)
|
||||
.root_name()
|
||||
.join(directory_in_worktree)
|
||||
.to_rel_path_buf()
|
||||
} else {
|
||||
directory_in_worktree.clone()
|
||||
directory_in_worktree.to_rel_path_buf()
|
||||
};
|
||||
|
||||
match path
|
||||
.components()
|
||||
.next_back()
|
||||
.and_then(|component| component.as_os_str().to_str())
|
||||
{
|
||||
match path.components().next_back() {
|
||||
Some(".zed") => {
|
||||
path.push("debug.json");
|
||||
path.push(RelPath::new("debug.json").unwrap());
|
||||
}
|
||||
Some(".vscode") => {
|
||||
path.push("launch.json");
|
||||
path.push(RelPath::new("launch.json").unwrap());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Some(path.display().to_string())
|
||||
path.display(project.path_style(cx)).to_string()
|
||||
})
|
||||
.unwrap_or_else(|_| Some(directory_in_worktree.display().to_string())),
|
||||
.ok(),
|
||||
Some(TaskSourceKind::AbsPath { abs_path, .. }) => {
|
||||
Some(abs_path.to_string_lossy().into_owned())
|
||||
}
|
||||
|
|
@ -1135,7 +1133,7 @@ impl DebugDelegate {
|
|||
id: _,
|
||||
directory_in_worktree: dir,
|
||||
id_base: _,
|
||||
} => dir.ends_with(".zed"),
|
||||
} => dir.ends_with(RelPath::new(".zed").unwrap()),
|
||||
_ => false,
|
||||
});
|
||||
|
||||
|
|
@ -1154,7 +1152,10 @@ impl DebugDelegate {
|
|||
id: _,
|
||||
directory_in_worktree: dir,
|
||||
id_base: _,
|
||||
} => !(hide_vscode && dir.ends_with(".vscode")),
|
||||
} => {
|
||||
!(hide_vscode
|
||||
&& dir.ends_with(RelPath::new(".vscode").unwrap()))
|
||||
}
|
||||
_ => true,
|
||||
})
|
||||
.filter(|(_, scenario)| valid_adapters.contains(&scenario.adapter))
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ use ui::{
|
|||
Divider, DividerColor, FluentBuilder as _, Indicator, IntoElement, ListItem, Render,
|
||||
StatefulInteractiveElement, Tooltip, WithScrollbar, prelude::*,
|
||||
};
|
||||
use util::rel_path::RelPath;
|
||||
use workspace::Workspace;
|
||||
use zed_actions::{ToggleEnableBreakpoint, UnsetBreakpoint};
|
||||
|
||||
|
|
@ -663,6 +664,7 @@ impl Render for BreakpointList {
|
|||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl ui::IntoElement {
|
||||
let breakpoints = self.breakpoint_store.read(cx).all_source_breakpoints(cx);
|
||||
self.breakpoints.clear();
|
||||
let path_style = self.worktree_store.read(cx).path_style();
|
||||
let weak = cx.weak_entity();
|
||||
let breakpoints = breakpoints.into_iter().flat_map(|(path, mut breakpoints)| {
|
||||
let relative_worktree_path = self
|
||||
|
|
@ -673,7 +675,7 @@ impl Render for BreakpointList {
|
|||
worktree
|
||||
.read(cx)
|
||||
.is_visible()
|
||||
.then(|| Path::new(worktree.read(cx).root_name()).join(relative_path))
|
||||
.then(|| worktree.read(cx).root_name().join(&relative_path))
|
||||
});
|
||||
breakpoints.sort_by_key(|breakpoint| breakpoint.row);
|
||||
let weak = weak.clone();
|
||||
|
|
@ -683,14 +685,9 @@ impl Render for BreakpointList {
|
|||
|
||||
let dir = relative_worktree_path
|
||||
.clone()
|
||||
.unwrap_or_else(|| PathBuf::from(&*breakpoint.path))
|
||||
.or_else(|| RelPath::from_std_path(&breakpoint.path, path_style).ok())?
|
||||
.parent()
|
||||
.and_then(|parent| {
|
||||
parent
|
||||
.to_str()
|
||||
.map(ToOwned::to_owned)
|
||||
.map(SharedString::from)
|
||||
});
|
||||
.map(|parent| SharedString::from(parent.display(path_style).to_string()));
|
||||
let name = file_name
|
||||
.to_str()
|
||||
.map(ToOwned::to_owned)
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ impl ModuleList {
|
|||
this.open_buffer(
|
||||
ProjectPath {
|
||||
worktree_id,
|
||||
path: relative_path.into(),
|
||||
path: relative_path,
|
||||
},
|
||||
cx,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -401,7 +401,7 @@ impl StackFrameList {
|
|||
this.open_buffer(
|
||||
ProjectPath {
|
||||
worktree_id,
|
||||
path: relative_path.into(),
|
||||
path: relative_path,
|
||||
},
|
||||
cx,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -181,7 +181,7 @@ impl StackTraceView {
|
|||
|
||||
let project_path = ProjectPath {
|
||||
worktree_id: worktree.read_with(cx, |tree, _| tree.id())?,
|
||||
path: relative_path.into(),
|
||||
path: relative_path,
|
||||
};
|
||||
|
||||
if let Some(buffer) = this
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ use std::{
|
|||
};
|
||||
use terminal_view::terminal_panel::TerminalPanel;
|
||||
use tests::{active_debug_session_panel, init_test, init_test_workspace};
|
||||
use util::path;
|
||||
use util::{path, rel_path::rel_path};
|
||||
use workspace::item::SaveOptions;
|
||||
use workspace::{Item, dock::Panel};
|
||||
|
||||
|
|
@ -1114,7 +1114,7 @@ async fn test_send_breakpoints_when_editor_has_been_saved(
|
|||
|
||||
let buffer = project
|
||||
.update(cx, |project, cx| {
|
||||
project.open_buffer((worktree_id, "main.rs"), cx)
|
||||
project.open_buffer((worktree_id, rel_path("main.rs")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
|
@ -1276,14 +1276,14 @@ async fn test_unsetting_breakpoints_on_clear_breakpoint_action(
|
|||
|
||||
let first = project
|
||||
.update(cx, |project, cx| {
|
||||
project.open_buffer((worktree_id, "main.rs"), cx)
|
||||
project.open_buffer((worktree_id, rel_path("main.rs")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let second = project
|
||||
.update(cx, |project, cx| {
|
||||
project.open_buffer((worktree_id, "second.rs"), cx)
|
||||
project.open_buffer((worktree_id, rel_path("second.rs")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
|
@ -1499,14 +1499,14 @@ async fn test_active_debug_line_setting(executor: BackgroundExecutor, cx: &mut T
|
|||
|
||||
let main_buffer = project
|
||||
.update(cx, |project, cx| {
|
||||
project.open_buffer((worktree_id, "main.rs"), cx)
|
||||
project.open_buffer((worktree_id, rel_path("main.rs")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let second_buffer = project
|
||||
.update(cx, |project, cx| {
|
||||
project.open_buffer((worktree_id, "second.rs"), cx)
|
||||
project.open_buffer((worktree_id, rel_path("second.rs")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use language::{Language, LanguageConfig, LanguageMatcher, tree_sitter_python, tr
|
|||
use project::{FakeFs, Project};
|
||||
use serde_json::json;
|
||||
use unindent::Unindent as _;
|
||||
use util::path;
|
||||
use util::{path, rel_path::rel_path};
|
||||
|
||||
use crate::{
|
||||
debugger_panel::DebugPanel,
|
||||
|
|
@ -215,7 +215,7 @@ fn main() {
|
|||
|
||||
let buffer = project
|
||||
.update(cx, |project, cx| {
|
||||
project.open_buffer((worktree_id, "main.rs"), cx)
|
||||
project.open_buffer((worktree_id, rel_path("main.rs")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
|
@ -1584,7 +1584,7 @@ def process_data(untyped_param, typed_param: int, another_typed: str):
|
|||
|
||||
let buffer = project
|
||||
.update(cx, |project, cx| {
|
||||
project.open_buffer((worktree_id, "main.py"), cx)
|
||||
project.open_buffer((worktree_id, rel_path("main.py")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
|
@ -2082,7 +2082,7 @@ async fn test_inline_values_util(
|
|||
|
||||
let buffer = project
|
||||
.update(cx, |project, cx| {
|
||||
project.open_buffer((worktree_id, "main.rs"), cx)
|
||||
project.open_buffer((worktree_id, rel_path("main.rs")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ use project::{FakeFs, Project};
|
|||
use serde_json::json;
|
||||
use std::sync::Arc;
|
||||
use unindent::Unindent as _;
|
||||
use util::path;
|
||||
use util::{path, rel_path::rel_path};
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_fetch_initial_stack_frames_and_go_to_stack_frame(
|
||||
|
|
@ -331,12 +331,7 @@ async fn test_select_stack_frame(executor: BackgroundExecutor, cx: &mut TestAppC
|
|||
let project_path = editors[0]
|
||||
.update(cx, |editor, cx| editor.project_path(cx))
|
||||
.unwrap();
|
||||
let expected = if cfg!(target_os = "windows") {
|
||||
"src\\test.js"
|
||||
} else {
|
||||
"src/test.js"
|
||||
};
|
||||
assert_eq!(expected, project_path.path.to_string_lossy());
|
||||
assert_eq!(rel_path("src/test.js"), project_path.path.as_ref());
|
||||
assert_eq!(test_file_content, editors[0].read(cx).text(cx));
|
||||
assert_eq!(
|
||||
vec![2..3],
|
||||
|
|
@ -399,12 +394,7 @@ async fn test_select_stack_frame(executor: BackgroundExecutor, cx: &mut TestAppC
|
|||
let project_path = editors[0]
|
||||
.update(cx, |editor, cx| editor.project_path(cx))
|
||||
.unwrap();
|
||||
let expected = if cfg!(target_os = "windows") {
|
||||
"src\\module.js"
|
||||
} else {
|
||||
"src/module.js"
|
||||
};
|
||||
assert_eq!(expected, project_path.path.to_string_lossy());
|
||||
assert_eq!(rel_path("src/module.js"), project_path.path.as_ref());
|
||||
assert_eq!(module_file_content, editors[0].read(cx).text(cx));
|
||||
assert_eq!(
|
||||
vec![0..1],
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ use std::{
|
|||
};
|
||||
use text::{Anchor, BufferSnapshot, OffsetRangeExt};
|
||||
use ui::{Button, ButtonStyle, Icon, IconName, Label, Tooltip, h_flex, prelude::*};
|
||||
use util::paths::PathExt;
|
||||
use workspace::{
|
||||
ItemHandle, ItemNavHistory, ToolbarItemLocation, Workspace,
|
||||
item::{BreadcrumbText, Item, ItemEvent, TabContentParams},
|
||||
|
|
@ -783,15 +782,16 @@ impl Item for BufferDiagnosticsEditor {
|
|||
}
|
||||
|
||||
// Builds the content to be displayed in the tab.
|
||||
fn tab_content(&self, params: TabContentParams, _window: &Window, _cx: &App) -> AnyElement {
|
||||
fn tab_content(&self, params: TabContentParams, _window: &Window, cx: &App) -> AnyElement {
|
||||
let path_style = self.project.read(cx).path_style(cx);
|
||||
let error_count = self.summary.error_count;
|
||||
let warning_count = self.summary.warning_count;
|
||||
let label = Label::new(
|
||||
self.project_path
|
||||
.path
|
||||
.file_name()
|
||||
.map(|f| f.to_sanitized_string())
|
||||
.unwrap_or_else(|| self.project_path.path.to_sanitized_string()),
|
||||
.map(|s| s.to_string())
|
||||
.unwrap_or_else(|| self.project_path.path.display(path_style).to_string()),
|
||||
);
|
||||
|
||||
h_flex()
|
||||
|
|
@ -827,11 +827,12 @@ impl Item for BufferDiagnosticsEditor {
|
|||
"Buffer Diagnostics".into()
|
||||
}
|
||||
|
||||
fn tab_tooltip_text(&self, _: &App) -> Option<SharedString> {
|
||||
fn tab_tooltip_text(&self, cx: &App) -> Option<SharedString> {
|
||||
let path_style = self.project.read(cx).path_style(cx);
|
||||
Some(
|
||||
format!(
|
||||
"Buffer Diagnostics - {}",
|
||||
self.project_path.path.to_sanitized_string()
|
||||
self.project_path.path.display(path_style)
|
||||
)
|
||||
.into(),
|
||||
)
|
||||
|
|
@ -848,7 +849,8 @@ impl Item for BufferDiagnosticsEditor {
|
|||
|
||||
impl Render for BufferDiagnosticsEditor {
|
||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let filename = self.project_path.path.to_sanitized_string();
|
||||
let path_style = self.project.read(cx).path_style(cx);
|
||||
let filename = self.project_path.path.display(path_style).to_string();
|
||||
let error_count = self.summary.error_count;
|
||||
let warning_count = match self.include_warnings {
|
||||
true => self.summary.warning_count,
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ use std::{
|
|||
str::FromStr,
|
||||
};
|
||||
use unindent::Unindent as _;
|
||||
use util::{RandomCharIter, path, post_inc};
|
||||
use util::{RandomCharIter, path, post_inc, rel_path::rel_path};
|
||||
|
||||
#[ctor::ctor]
|
||||
fn init_logger() {
|
||||
|
|
@ -1609,7 +1609,7 @@ async fn test_buffer_diagnostics(cx: &mut TestAppContext) {
|
|||
worktree_id: project.read_with(cx, |project, cx| {
|
||||
project.worktrees(cx).next().unwrap().read(cx).id()
|
||||
}),
|
||||
path: Arc::from(Path::new("main.rs")),
|
||||
path: rel_path("main.rs").into(),
|
||||
};
|
||||
let buffer = project
|
||||
.update(cx, |project, cx| {
|
||||
|
|
@ -1763,7 +1763,7 @@ async fn test_buffer_diagnostics_without_warnings(cx: &mut TestAppContext) {
|
|||
worktree_id: project.read_with(cx, |project, cx| {
|
||||
project.worktrees(cx).next().unwrap().read(cx).id()
|
||||
}),
|
||||
path: Arc::from(Path::new("main.rs")),
|
||||
path: rel_path("main.rs").into(),
|
||||
};
|
||||
let buffer = project
|
||||
.update(cx, |project, cx| {
|
||||
|
|
@ -1892,7 +1892,7 @@ async fn test_buffer_diagnostics_multiple_servers(cx: &mut TestAppContext) {
|
|||
worktree_id: project.read_with(cx, |project, cx| {
|
||||
project.worktrees(cx).next().unwrap().read(cx).id()
|
||||
}),
|
||||
path: Arc::from(Path::new("main.rs")),
|
||||
path: rel_path("main.rs").into(),
|
||||
};
|
||||
let buffer = project
|
||||
.update(cx, |project, cx| {
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ use std::collections::{HashMap, HashSet};
|
|||
use std::io::{self, Read};
|
||||
use std::process;
|
||||
use std::sync::{LazyLock, OnceLock};
|
||||
use util::paths::PathExt;
|
||||
|
||||
static KEYMAP_MACOS: LazyLock<KeymapFile> = LazyLock::new(|| {
|
||||
load_keymap("keymaps/default-macos.json").expect("Failed to load MacOS keymap")
|
||||
|
|
@ -345,7 +344,7 @@ fn handle_postprocessing() -> Result<()> {
|
|||
let mut queue = Vec::with_capacity(64);
|
||||
queue.push(root_dir.clone());
|
||||
while let Some(dir) = queue.pop() {
|
||||
for entry in std::fs::read_dir(&dir).context(dir.to_sanitized_string())? {
|
||||
for entry in std::fs::read_dir(&dir).context("failed to read docs dir")? {
|
||||
let Ok(entry) = entry else {
|
||||
continue;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -324,7 +324,7 @@ impl SyntaxIndex {
|
|||
cx.spawn(async move |_this, cx| {
|
||||
let loaded_file = load_task.await?;
|
||||
let language = language_registry
|
||||
.language_for_file_path(&project_path.path)
|
||||
.language_for_file_path(&project_path.path.as_std_path())
|
||||
.await
|
||||
.ok();
|
||||
|
||||
|
|
@ -549,7 +549,7 @@ impl SyntaxIndexState {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::{path::Path, sync::Arc};
|
||||
use std::sync::Arc;
|
||||
|
||||
use gpui::TestAppContext;
|
||||
use indoc::indoc;
|
||||
|
|
@ -558,7 +558,7 @@ mod tests {
|
|||
use serde_json::json;
|
||||
use settings::SettingsStore;
|
||||
use text::OffsetRangeExt as _;
|
||||
use util::path;
|
||||
use util::{path, rel_path::rel_path};
|
||||
|
||||
use crate::syntax_index::SyntaxIndex;
|
||||
|
||||
|
|
@ -739,7 +739,7 @@ mod tests {
|
|||
.read(cx)
|
||||
.path_for_entry(*project_entry_id, cx)
|
||||
.unwrap();
|
||||
assert_eq!(project_path.path.as_ref(), Path::new(path),);
|
||||
assert_eq!(project_path.path.as_ref(), rel_path(path),);
|
||||
declaration
|
||||
} else {
|
||||
panic!("Expected a buffer declaration, found {:?}", declaration);
|
||||
|
|
@ -764,7 +764,7 @@ mod tests {
|
|||
.unwrap()
|
||||
.path
|
||||
.as_ref(),
|
||||
Path::new(path),
|
||||
rel_path(path),
|
||||
);
|
||||
declaration
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ use language::Language;
|
|||
use project::lsp_store::lsp_ext_command::SwitchSourceHeaderResult;
|
||||
use rpc::proto;
|
||||
use url::Url;
|
||||
use util::paths::PathStyle;
|
||||
use workspace::{OpenOptions, OpenVisible};
|
||||
|
||||
use crate::lsp_ext::find_specific_language_server_in_selection;
|
||||
|
|
@ -38,7 +39,11 @@ pub fn switch_source_header(
|
|||
let upstream_client = project.read(cx).lsp_store().read(cx).upstream_client();
|
||||
cx.spawn_in(window, async move |_editor, cx| {
|
||||
let source_file = buffer.read_with(cx, |buffer, _| {
|
||||
buffer.file().map(|file| file.path()).map(|path| path.to_string_lossy().to_string()).unwrap_or_else(|| "Unknown".to_string())
|
||||
buffer
|
||||
.file()
|
||||
.map(|file| file.path())
|
||||
.map(|path| path.display(PathStyle::local()).to_string())
|
||||
.unwrap_or_else(|| "Unknown".to_string())
|
||||
})?;
|
||||
|
||||
let switch_source_header = if let Some((client, project_id)) = upstream_client {
|
||||
|
|
@ -53,18 +58,22 @@ pub fn switch_source_header(
|
|||
.context("lsp ext switch source header proto request")?;
|
||||
SwitchSourceHeaderResult(response.target_file)
|
||||
} else {
|
||||
project.update(cx, |project, cx| {
|
||||
project.request_lsp(
|
||||
buffer,
|
||||
project::LanguageServerToQuery::Other(server_to_query),
|
||||
project::lsp_store::lsp_ext_command::SwitchSourceHeader,
|
||||
cx,
|
||||
)
|
||||
})?.await.with_context(|| format!("Switch source/header LSP request for path \"{source_file}\" failed"))?
|
||||
project
|
||||
.update(cx, |project, cx| {
|
||||
project.request_lsp(
|
||||
buffer,
|
||||
project::LanguageServerToQuery::Other(server_to_query),
|
||||
project::lsp_store::lsp_ext_command::SwitchSourceHeader,
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!("Switch source/header LSP request for path \"{source_file}\" failed")
|
||||
})?
|
||||
};
|
||||
|
||||
if switch_source_header.0.is_empty() {
|
||||
log::info!("Clangd returned an empty string when requesting to switch source/header from \"{source_file}\"" );
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
|
|
@ -75,18 +84,24 @@ pub fn switch_source_header(
|
|||
)
|
||||
})?;
|
||||
|
||||
let path = goto.to_file_path().map_err(|()| {
|
||||
anyhow::anyhow!("URL conversion to file path failed for \"{goto}\"")
|
||||
})?;
|
||||
let path = goto
|
||||
.to_file_path()
|
||||
.map_err(|()| anyhow::anyhow!("URL conversion to file path failed for \"{goto}\""))?;
|
||||
|
||||
workspace
|
||||
.update_in(cx, |workspace, window, cx| {
|
||||
workspace.open_abs_path(path, OpenOptions { visible: Some(OpenVisible::None), ..Default::default() }, window, cx)
|
||||
workspace.open_abs_path(
|
||||
path,
|
||||
OpenOptions {
|
||||
visible: Some(OpenVisible::None),
|
||||
..Default::default()
|
||||
},
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"Switch source/header could not open \"{goto}\" in workspace"
|
||||
)
|
||||
format!("Switch source/header could not open \"{goto}\" in workspace")
|
||||
})?
|
||||
.await
|
||||
.map(|_| ())
|
||||
|
|
|
|||
|
|
@ -2494,7 +2494,7 @@ impl Editor {
|
|||
if let Some(extension) = singleton_buffer
|
||||
.read(cx)
|
||||
.file()
|
||||
.and_then(|file| file.path().extension()?.to_str())
|
||||
.and_then(|file| file.path().extension())
|
||||
{
|
||||
key_context.set("extension", extension.to_string());
|
||||
}
|
||||
|
|
@ -7603,7 +7603,7 @@ impl Editor {
|
|||
let extension = buffer
|
||||
.read(cx)
|
||||
.file()
|
||||
.and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
|
||||
.and_then(|file| Some(file.path().extension()?.to_string()));
|
||||
|
||||
let event_type = match accepted {
|
||||
true => "Edit Prediction Accepted",
|
||||
|
|
@ -19263,10 +19263,6 @@ impl Editor {
|
|||
{
|
||||
return Some(dir.to_owned());
|
||||
}
|
||||
|
||||
if let Some(project_path) = buffer.read(cx).project_path(cx) {
|
||||
return Some(project_path.path.to_path_buf());
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
|
|
@ -19294,16 +19290,6 @@ impl Editor {
|
|||
})
|
||||
}
|
||||
|
||||
fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
|
||||
self.active_excerpt(cx).and_then(|(_, buffer, _)| {
|
||||
let project_path = buffer.read(cx).project_path(cx)?;
|
||||
let project = self.project()?.read(cx);
|
||||
let entry = project.entry_for_path(&project_path, cx)?;
|
||||
let path = entry.path.to_path_buf();
|
||||
Some(path)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn reveal_in_finder(
|
||||
&mut self,
|
||||
_: &RevealInFileManager,
|
||||
|
|
@ -19336,9 +19322,12 @@ impl Editor {
|
|||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if let Some(path) = self.target_file_path(cx)
|
||||
&& let Some(path) = path.to_str()
|
||||
{
|
||||
if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
|
||||
let project = self.project()?.read(cx);
|
||||
let path = buffer.read(cx).file()?.path();
|
||||
let path = path.display(project.path_style(cx));
|
||||
Some(path)
|
||||
}) {
|
||||
cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
|
||||
} else {
|
||||
cx.propagate();
|
||||
|
|
@ -19414,16 +19403,14 @@ impl Editor {
|
|||
) {
|
||||
if let Some(file) = self.target_file(cx)
|
||||
&& let Some(file_stem) = file.path().file_stem()
|
||||
&& let Some(name) = file_stem.to_str()
|
||||
{
|
||||
cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
|
||||
cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
|
||||
if let Some(file) = self.target_file(cx)
|
||||
&& let Some(file_name) = file.path().file_name()
|
||||
&& let Some(name) = file_name.to_str()
|
||||
&& let Some(name) = file.path().file_name()
|
||||
{
|
||||
cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
|
||||
}
|
||||
|
|
@ -19691,9 +19678,8 @@ impl Editor {
|
|||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let selection = self.selections.newest::<Point>(cx).start.row + 1;
|
||||
if let Some(file) = self.target_file(cx)
|
||||
&& let Some(path) = file.path().to_str()
|
||||
{
|
||||
if let Some(file) = self.target_file(cx) {
|
||||
let path = file.path().display(file.path_style(cx));
|
||||
cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ use text::ToPoint as _;
|
|||
use unindent::Unindent;
|
||||
use util::{
|
||||
assert_set_eq, path,
|
||||
rel_path::rel_path,
|
||||
test::{TextRangeMarker, marked_text_ranges, marked_text_ranges_by, sample_text},
|
||||
uri,
|
||||
};
|
||||
|
|
@ -11142,19 +11143,19 @@ async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
|
|||
|
||||
let buffer_1 = project
|
||||
.update(cx, |project, cx| {
|
||||
project.open_buffer((worktree_id, "main.rs"), cx)
|
||||
project.open_buffer((worktree_id, rel_path("main.rs")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
let buffer_2 = project
|
||||
.update(cx, |project, cx| {
|
||||
project.open_buffer((worktree_id, "other.rs"), cx)
|
||||
project.open_buffer((worktree_id, rel_path("other.rs")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
let buffer_3 = project
|
||||
.update(cx, |project, cx| {
|
||||
project.open_buffer((worktree_id, "lib.rs"), cx)
|
||||
project.open_buffer((worktree_id, rel_path("lib.rs")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
|
@ -11329,19 +11330,19 @@ async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
|
|||
// Open three buffers
|
||||
let buffer_1 = project
|
||||
.update(cx, |project, cx| {
|
||||
project.open_buffer((worktree_id, "file1.rs"), cx)
|
||||
project.open_buffer((worktree_id, rel_path("file1.rs")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
let buffer_2 = project
|
||||
.update(cx, |project, cx| {
|
||||
project.open_buffer((worktree_id, "file2.rs"), cx)
|
||||
project.open_buffer((worktree_id, rel_path("file2.rs")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
let buffer_3 = project
|
||||
.update(cx, |project, cx| {
|
||||
project.open_buffer((worktree_id, "file3.rs"), cx)
|
||||
project.open_buffer((worktree_id, rel_path("file3.rs")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
|
@ -14677,7 +14678,7 @@ async fn test_multiline_completion(cx: &mut TestAppContext) {
|
|||
.unwrap();
|
||||
let editor = workspace
|
||||
.update(cx, |workspace, window, cx| {
|
||||
workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
|
||||
workspace.open_path((worktree_id, rel_path("main.ts")), None, true, window, cx)
|
||||
})
|
||||
.unwrap()
|
||||
.await
|
||||
|
|
@ -16394,7 +16395,7 @@ async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
|
|||
leader.update(cx, |leader, cx| {
|
||||
leader.buffer.update(cx, |multibuffer, cx| {
|
||||
multibuffer.set_excerpts_for_path(
|
||||
PathKey::namespaced(1, Arc::from(Path::new("b.txt"))),
|
||||
PathKey::namespaced(1, "b.txt".into()),
|
||||
buffer_1.clone(),
|
||||
vec![
|
||||
Point::row_range(0..3),
|
||||
|
|
@ -16405,7 +16406,7 @@ async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
|
|||
cx,
|
||||
);
|
||||
multibuffer.set_excerpts_for_path(
|
||||
PathKey::namespaced(1, Arc::from(Path::new("a.txt"))),
|
||||
PathKey::namespaced(1, "a.txt".into()),
|
||||
buffer_2.clone(),
|
||||
vec![Point::row_range(0..6), Point::row_range(8..12)],
|
||||
0,
|
||||
|
|
@ -16897,7 +16898,7 @@ async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
|
|||
.unwrap();
|
||||
let editor_handle = workspace
|
||||
.update(cx, |workspace, window, cx| {
|
||||
workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
|
||||
workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
|
||||
})
|
||||
.unwrap()
|
||||
.await
|
||||
|
|
@ -20878,9 +20879,9 @@ async fn test_display_diff_hunks(cx: &mut TestAppContext) {
|
|||
fs.set_head_for_repo(
|
||||
path!("/test/.git").as_ref(),
|
||||
&[
|
||||
("file-1".into(), "one\n".into()),
|
||||
("file-2".into(), "two\n".into()),
|
||||
("file-3".into(), "three\n".into()),
|
||||
("file-1", "one\n".into()),
|
||||
("file-2", "two\n".into()),
|
||||
("file-3", "three\n".into()),
|
||||
],
|
||||
"deadbeef",
|
||||
);
|
||||
|
|
@ -20904,7 +20905,7 @@ async fn test_display_diff_hunks(cx: &mut TestAppContext) {
|
|||
for buffer in &buffers {
|
||||
let snapshot = buffer.read(cx).snapshot();
|
||||
multibuffer.set_excerpts_for_path(
|
||||
PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
|
||||
PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().as_str().into()),
|
||||
buffer.clone(),
|
||||
vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
|
||||
2,
|
||||
|
|
@ -21657,19 +21658,19 @@ async fn test_folding_buffers(cx: &mut TestAppContext) {
|
|||
|
||||
let buffer_1 = project
|
||||
.update(cx, |project, cx| {
|
||||
project.open_buffer((worktree_id, "first.rs"), cx)
|
||||
project.open_buffer((worktree_id, rel_path("first.rs")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
let buffer_2 = project
|
||||
.update(cx, |project, cx| {
|
||||
project.open_buffer((worktree_id, "second.rs"), cx)
|
||||
project.open_buffer((worktree_id, rel_path("second.rs")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
let buffer_3 = project
|
||||
.update(cx, |project, cx| {
|
||||
project.open_buffer((worktree_id, "third.rs"), cx)
|
||||
project.open_buffer((worktree_id, rel_path("third.rs")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
|
@ -21825,19 +21826,19 @@ async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
|
|||
|
||||
let buffer_1 = project
|
||||
.update(cx, |project, cx| {
|
||||
project.open_buffer((worktree_id, "first.rs"), cx)
|
||||
project.open_buffer((worktree_id, rel_path("first.rs")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
let buffer_2 = project
|
||||
.update(cx, |project, cx| {
|
||||
project.open_buffer((worktree_id, "second.rs"), cx)
|
||||
project.open_buffer((worktree_id, rel_path("second.rs")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
let buffer_3 = project
|
||||
.update(cx, |project, cx| {
|
||||
project.open_buffer((worktree_id, "third.rs"), cx)
|
||||
project.open_buffer((worktree_id, rel_path("third.rs")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
|
@ -21960,7 +21961,7 @@ async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut Test
|
|||
|
||||
let buffer_1 = project
|
||||
.update(cx, |project, cx| {
|
||||
project.open_buffer((worktree_id, "main.rs"), cx)
|
||||
project.open_buffer((worktree_id, rel_path("main.rs")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
|
@ -22499,7 +22500,7 @@ async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
|
|||
|
||||
let buffer = project
|
||||
.update(cx, |project, cx| {
|
||||
project.open_buffer((worktree_id, "main.rs"), cx)
|
||||
project.open_buffer((worktree_id, rel_path("main.rs")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
|
@ -22613,7 +22614,7 @@ async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
|
|||
|
||||
let buffer = project
|
||||
.update(cx, |project, cx| {
|
||||
project.open_buffer((worktree_id, "main.rs"), cx)
|
||||
project.open_buffer((worktree_id, rel_path("main.rs")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
|
@ -22783,7 +22784,7 @@ async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
|
|||
|
||||
let buffer = project
|
||||
.update(cx, |project, cx| {
|
||||
project.open_buffer((worktree_id, "main.rs"), cx)
|
||||
project.open_buffer((worktree_id, rel_path("main.rs")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
|
@ -23371,7 +23372,7 @@ println!("5");
|
|||
let editor_1 = workspace
|
||||
.update_in(cx, |workspace, window, cx| {
|
||||
workspace.open_path(
|
||||
(worktree_id, "main.rs"),
|
||||
(worktree_id, rel_path("main.rs")),
|
||||
Some(pane_1.downgrade()),
|
||||
true,
|
||||
window,
|
||||
|
|
@ -23414,7 +23415,7 @@ println!("5");
|
|||
let editor_2 = workspace
|
||||
.update_in(cx, |workspace, window, cx| {
|
||||
workspace.open_path(
|
||||
(worktree_id, "main.rs"),
|
||||
(worktree_id, rel_path("main.rs")),
|
||||
Some(pane_2.downgrade()),
|
||||
true,
|
||||
window,
|
||||
|
|
@ -23453,7 +23454,7 @@ println!("5");
|
|||
let _other_editor_1 = workspace
|
||||
.update_in(cx, |workspace, window, cx| {
|
||||
workspace.open_path(
|
||||
(worktree_id, "lib.rs"),
|
||||
(worktree_id, rel_path("lib.rs")),
|
||||
Some(pane_1.downgrade()),
|
||||
true,
|
||||
window,
|
||||
|
|
@ -23489,7 +23490,7 @@ println!("5");
|
|||
let _other_editor_2 = workspace
|
||||
.update_in(cx, |workspace, window, cx| {
|
||||
workspace.open_path(
|
||||
(worktree_id, "lib.rs"),
|
||||
(worktree_id, rel_path("lib.rs")),
|
||||
Some(pane_2.downgrade()),
|
||||
true,
|
||||
window,
|
||||
|
|
@ -23526,7 +23527,7 @@ println!("5");
|
|||
let _editor_1_reopened = workspace
|
||||
.update_in(cx, |workspace, window, cx| {
|
||||
workspace.open_path(
|
||||
(worktree_id, "main.rs"),
|
||||
(worktree_id, rel_path("main.rs")),
|
||||
Some(pane_1.downgrade()),
|
||||
true,
|
||||
window,
|
||||
|
|
@ -23540,7 +23541,7 @@ println!("5");
|
|||
let _editor_2_reopened = workspace
|
||||
.update_in(cx, |workspace, window, cx| {
|
||||
workspace.open_path(
|
||||
(worktree_id, "main.rs"),
|
||||
(worktree_id, rel_path("main.rs")),
|
||||
Some(pane_2.downgrade()),
|
||||
true,
|
||||
window,
|
||||
|
|
@ -23634,7 +23635,7 @@ println!("5");
|
|||
let editor = workspace
|
||||
.update_in(cx, |workspace, window, cx| {
|
||||
workspace.open_path(
|
||||
(worktree_id, "main.rs"),
|
||||
(worktree_id, rel_path("main.rs")),
|
||||
Some(pane.downgrade()),
|
||||
true,
|
||||
window,
|
||||
|
|
@ -23693,7 +23694,7 @@ println!("5");
|
|||
let _editor_reopened = workspace
|
||||
.update_in(cx, |workspace, window, cx| {
|
||||
workspace.open_path(
|
||||
(worktree_id, "main.rs"),
|
||||
(worktree_id, rel_path("main.rs")),
|
||||
Some(pane.downgrade()),
|
||||
true,
|
||||
window,
|
||||
|
|
@ -23860,7 +23861,7 @@ async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
|
|||
.unwrap();
|
||||
let editor = workspace
|
||||
.update(cx, |workspace, window, cx| {
|
||||
workspace.open_path((worktree_id, "file.html"), None, true, window, cx)
|
||||
workspace.open_path((worktree_id, rel_path("file.html")), None, true, window, cx)
|
||||
})
|
||||
.unwrap()
|
||||
.await
|
||||
|
|
@ -24054,7 +24055,7 @@ async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
|
|||
let main_editor = workspace
|
||||
.update_in(cx, |workspace, window, cx| {
|
||||
workspace.open_path(
|
||||
(worktree_id, "main.rs"),
|
||||
(worktree_id, rel_path("main.rs")),
|
||||
Some(pane.downgrade()),
|
||||
true,
|
||||
window,
|
||||
|
|
@ -25636,7 +25637,7 @@ async fn test_document_colors(cx: &mut TestAppContext) {
|
|||
.path();
|
||||
assert_eq!(
|
||||
editor_file.as_ref(),
|
||||
Path::new("first.rs"),
|
||||
rel_path("first.rs"),
|
||||
"Both editors should be opened for the same file"
|
||||
)
|
||||
}
|
||||
|
|
@ -25816,7 +25817,7 @@ async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
|
|||
|
||||
let handle = workspace
|
||||
.update_in(cx, |workspace, window, cx| {
|
||||
let project_path = (worktree_id, "one.pdf");
|
||||
let project_path = (worktree_id, rel_path("one.pdf"));
|
||||
workspace.open_path(project_path, None, true, window, cx)
|
||||
})
|
||||
.await
|
||||
|
|
|
|||
|
|
@ -3779,13 +3779,15 @@ impl EditorElement {
|
|||
.as_ref()
|
||||
.map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
|
||||
.unwrap_or_default();
|
||||
let can_open_excerpts = Editor::can_open_excerpts_in_file(for_excerpt.buffer.file());
|
||||
let file = for_excerpt.buffer.file();
|
||||
let can_open_excerpts = Editor::can_open_excerpts_in_file(file);
|
||||
let path_style = file.map(|file| file.path_style(cx));
|
||||
let relative_path = for_excerpt.buffer.resolve_file_path(cx, include_root);
|
||||
let filename = relative_path
|
||||
.as_ref()
|
||||
.and_then(|path| Some(path.file_name()?.to_string_lossy().to_string()));
|
||||
let parent_path = relative_path.as_ref().and_then(|path| {
|
||||
Some(path.parent()?.to_string_lossy().to_string() + std::path::MAIN_SEPARATOR_STR)
|
||||
Some(path.parent()?.to_string_lossy().to_string() + path_style?.separator())
|
||||
});
|
||||
let focus_handle = editor.focus_handle(cx);
|
||||
let colors = cx.theme().colors();
|
||||
|
|
@ -3990,12 +3992,13 @@ impl EditorElement {
|
|||
&& let Some(worktree) =
|
||||
project.read(cx).worktree_for_id(file.worktree_id(cx), cx)
|
||||
{
|
||||
let path_style = file.path_style(cx);
|
||||
let worktree = worktree.read(cx);
|
||||
let relative_path = file.path();
|
||||
let entry_for_path = worktree.entry_for_path(relative_path);
|
||||
let abs_path = entry_for_path.map(|e| {
|
||||
e.canonical_path.as_deref().map_or_else(
|
||||
|| worktree.abs_path().join(relative_path),
|
||||
|| worktree.absolutize(relative_path),
|
||||
Path::to_path_buf,
|
||||
)
|
||||
});
|
||||
|
|
@ -4031,7 +4034,7 @@ impl EditorElement {
|
|||
Some(Box::new(zed_actions::workspace::CopyRelativePath)),
|
||||
window.handler_for(&editor, move |_, _, cx| {
|
||||
cx.write_to_clipboard(ClipboardItem::new_string(
|
||||
relative_path.to_string_lossy().to_string(),
|
||||
relative_path.display(path_style).to_string(),
|
||||
));
|
||||
}),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -698,6 +698,7 @@ async fn parse_commit_messages(
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use git::repository::repo_path;
|
||||
use gpui::Context;
|
||||
use language::{Point, Rope};
|
||||
use project::FakeFs;
|
||||
|
|
@ -850,7 +851,7 @@ mod tests {
|
|||
fs.set_blame_for_repo(
|
||||
Path::new("/my-repo/.git"),
|
||||
vec![(
|
||||
"file.txt".into(),
|
||||
repo_path("file.txt"),
|
||||
Blame {
|
||||
entries: vec![
|
||||
blame_entry("1b1b1b", 0..1),
|
||||
|
|
@ -967,7 +968,7 @@ mod tests {
|
|||
fs.set_blame_for_repo(
|
||||
Path::new(path!("/my-repo/.git")),
|
||||
vec![(
|
||||
"file.txt".into(),
|
||||
repo_path("file.txt"),
|
||||
Blame {
|
||||
entries: vec![blame_entry("1b1b1b", 0..4)],
|
||||
..Default::default()
|
||||
|
|
@ -1135,7 +1136,7 @@ mod tests {
|
|||
fs.set_blame_for_repo(
|
||||
Path::new(path!("/my-repo/.git")),
|
||||
vec![(
|
||||
"file.txt".into(),
|
||||
repo_path("file.txt"),
|
||||
Blame {
|
||||
entries: blame_entries,
|
||||
..Default::default()
|
||||
|
|
@ -1178,7 +1179,7 @@ mod tests {
|
|||
fs.set_blame_for_repo(
|
||||
Path::new(path!("/my-repo/.git")),
|
||||
vec![(
|
||||
"file.txt".into(),
|
||||
repo_path("file.txt"),
|
||||
Blame {
|
||||
entries: blame_entries,
|
||||
..Default::default()
|
||||
|
|
|
|||
|
|
@ -651,7 +651,7 @@ impl Item for Editor {
|
|||
|
||||
fn tab_content_text(&self, detail: usize, cx: &App) -> SharedString {
|
||||
if let Some(path) = path_for_buffer(&self.buffer, detail, true, cx) {
|
||||
path.to_string_lossy().to_string().into()
|
||||
path.to_string().into()
|
||||
} else {
|
||||
// Use the same logic as the displayed title for consistency
|
||||
self.buffer.read(cx).title(cx).to_string().into()
|
||||
|
|
@ -667,7 +667,7 @@ impl Item for Editor {
|
|||
.file_icons
|
||||
.then(|| {
|
||||
path_for_buffer(&self.buffer, 0, true, cx)
|
||||
.and_then(|path| FileIcons::get_icon(path.as_ref(), cx))
|
||||
.and_then(|path| FileIcons::get_icon(Path::new(&*path), cx))
|
||||
})
|
||||
.flatten()
|
||||
.map(Icon::from_path)
|
||||
|
|
@ -703,8 +703,7 @@ impl Item for Editor {
|
|||
|
||||
let description = params.detail.and_then(|detail| {
|
||||
let path = path_for_buffer(&self.buffer, detail, false, cx)?;
|
||||
let description = path.to_string_lossy();
|
||||
let description = description.trim();
|
||||
let description = path.trim();
|
||||
|
||||
if description.is_empty() {
|
||||
return None;
|
||||
|
|
@ -898,10 +897,7 @@ impl Item for Editor {
|
|||
.as_singleton()
|
||||
.expect("cannot call save_as on an excerpt list");
|
||||
|
||||
let file_extension = path
|
||||
.path
|
||||
.extension()
|
||||
.map(|a| a.to_string_lossy().to_string());
|
||||
let file_extension = path.path.extension().map(|a| a.to_string());
|
||||
self.report_editor_event(
|
||||
ReportEditorEvent::Saved { auto_saved: false },
|
||||
file_extension,
|
||||
|
|
@ -1167,7 +1163,7 @@ impl SerializableItem for Editor {
|
|||
let (worktree, path) = project.find_worktree(&abs_path, cx)?;
|
||||
let project_path = ProjectPath {
|
||||
worktree_id: worktree.read(cx).id(),
|
||||
path: path.into(),
|
||||
path: path,
|
||||
};
|
||||
Some(project.open_path(project_path, cx))
|
||||
});
|
||||
|
|
@ -1288,7 +1284,7 @@ impl SerializableItem for Editor {
|
|||
project
|
||||
.read(cx)
|
||||
.worktree_for_id(worktree_id, cx)
|
||||
.and_then(|worktree| worktree.read(cx).absolutize(file.path()).ok())
|
||||
.map(|worktree| worktree.read(cx).absolutize(file.path()))
|
||||
.or_else(|| {
|
||||
let full_path = file.full_path(cx);
|
||||
let project_path = project.read(cx).find_project_path(&full_path, cx)?;
|
||||
|
|
@ -1882,7 +1878,7 @@ fn path_for_buffer<'a>(
|
|||
height: usize,
|
||||
include_filename: bool,
|
||||
cx: &'a App,
|
||||
) -> Option<Cow<'a, Path>> {
|
||||
) -> Option<Cow<'a, str>> {
|
||||
let file = buffer.read(cx).as_singleton()?.read(cx).file()?;
|
||||
path_for_file(file.as_ref(), height, include_filename, cx)
|
||||
}
|
||||
|
|
@ -1892,7 +1888,7 @@ fn path_for_file<'a>(
|
|||
mut height: usize,
|
||||
include_filename: bool,
|
||||
cx: &'a App,
|
||||
) -> Option<Cow<'a, Path>> {
|
||||
) -> Option<Cow<'a, str>> {
|
||||
// Ensure we always render at least the filename.
|
||||
height += 1;
|
||||
|
||||
|
|
@ -1906,22 +1902,21 @@ fn path_for_file<'a>(
|
|||
}
|
||||
}
|
||||
|
||||
// Here we could have just always used `full_path`, but that is very
|
||||
// allocation-heavy and so we try to use a `Cow<Path>` if we haven't
|
||||
// traversed all the way up to the worktree's root.
|
||||
// The full_path method allocates, so avoid calling it if height is zero.
|
||||
if height > 0 {
|
||||
let full_path = file.full_path(cx);
|
||||
if include_filename {
|
||||
Some(full_path.into())
|
||||
} else {
|
||||
Some(full_path.parent()?.to_path_buf().into())
|
||||
let mut full_path = file.full_path(cx);
|
||||
if !include_filename {
|
||||
if !full_path.pop() {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
Some(full_path.to_string_lossy().to_string().into())
|
||||
} else {
|
||||
let mut path = file.path().strip_prefix(prefix).ok()?;
|
||||
if !include_filename {
|
||||
path = path.parent()?;
|
||||
}
|
||||
Some(path.into())
|
||||
Some(path.display(file.path_style(cx)))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1936,12 +1931,12 @@ mod tests {
|
|||
use language::{LanguageMatcher, TestFile};
|
||||
use project::FakeFs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use util::path;
|
||||
use util::{path, rel_path::RelPath};
|
||||
|
||||
#[gpui::test]
|
||||
fn test_path_for_file(cx: &mut App) {
|
||||
let file = TestFile {
|
||||
path: Path::new("").into(),
|
||||
path: RelPath::empty().into(),
|
||||
root_name: String::new(),
|
||||
local_root: None,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -217,15 +217,7 @@ pub fn editor_content_with_blocks(editor: &Entity<Editor>, cx: &mut VisualTestCo
|
|||
height,
|
||||
} => {
|
||||
lines[row.0 as usize].push_str(&cx.update(|_, cx| {
|
||||
format!(
|
||||
"§ {}",
|
||||
first_excerpt
|
||||
.buffer
|
||||
.file()
|
||||
.unwrap()
|
||||
.file_name(cx)
|
||||
.to_string_lossy()
|
||||
)
|
||||
format!("§ {}", first_excerpt.buffer.file().unwrap().file_name(cx))
|
||||
}));
|
||||
for row in row.0 + 1..row.0 + height {
|
||||
lines[row as usize].push_str("§ -----");
|
||||
|
|
@ -237,17 +229,11 @@ pub fn editor_content_with_blocks(editor: &Entity<Editor>, cx: &mut VisualTestCo
|
|||
}
|
||||
}
|
||||
Block::BufferHeader { excerpt, height } => {
|
||||
lines[row.0 as usize].push_str(&cx.update(|_, cx| {
|
||||
format!(
|
||||
"§ {}",
|
||||
excerpt
|
||||
.buffer
|
||||
.file()
|
||||
.unwrap()
|
||||
.file_name(cx)
|
||||
.to_string_lossy()
|
||||
)
|
||||
}));
|
||||
lines[row.0 as usize].push_str(
|
||||
&cx.update(|_, cx| {
|
||||
format!("§ {}", excerpt.buffer.file().unwrap().file_name(cx))
|
||||
}),
|
||||
);
|
||||
for row in row.0 + 1..row.0 + height {
|
||||
lines[row as usize].push_str("§ -----");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -296,7 +296,7 @@ impl EditorTestContext {
|
|||
let path = self.update_buffer(|buffer, _| buffer.file().unwrap().path().clone());
|
||||
fs.set_head_for_repo(
|
||||
&Self::root_path().join(".git"),
|
||||
&[(path.into(), diff_base.to_string())],
|
||||
&[(path.as_str(), diff_base.to_string())],
|
||||
"deadbeef",
|
||||
);
|
||||
self.cx.run_until_parked();
|
||||
|
|
@ -317,7 +317,7 @@ impl EditorTestContext {
|
|||
let path = self.update_buffer(|buffer, _| buffer.file().unwrap().path().clone());
|
||||
fs.set_index_for_repo(
|
||||
&Self::root_path().join(".git"),
|
||||
&[(path.into(), diff_base.to_string())],
|
||||
&[(path.as_str(), diff_base.to_string())],
|
||||
);
|
||||
self.cx.run_until_parked();
|
||||
}
|
||||
|
|
@ -329,7 +329,7 @@ impl EditorTestContext {
|
|||
let path = self.update_buffer(|buffer, _| buffer.file().unwrap().path().clone());
|
||||
let mut found = None;
|
||||
fs.with_git_state(&Self::root_path().join(".git"), false, |git_state| {
|
||||
found = git_state.index_contents.get(path.as_ref()).cloned();
|
||||
found = git_state.index_contents.get(&path.into()).cloned();
|
||||
})
|
||||
.unwrap();
|
||||
assert_eq!(expected, found.as_deref());
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
use std::{
|
||||
error::Error,
|
||||
fmt::{self, Debug},
|
||||
path::Path,
|
||||
sync::{Arc, Mutex},
|
||||
time::Duration,
|
||||
};
|
||||
|
|
@ -20,6 +19,7 @@ use collections::HashMap;
|
|||
use futures::{FutureExt as _, StreamExt, channel::mpsc, select_biased};
|
||||
use gpui::{App, AppContext, AsyncApp, Entity};
|
||||
use language_model::{LanguageModel, Role, StopReason};
|
||||
use util::rel_path::RelPath;
|
||||
|
||||
pub const THREAD_EVENT_TIMEOUT: Duration = Duration::from_secs(60 * 2);
|
||||
|
||||
|
|
@ -354,7 +354,7 @@ impl ExampleContext {
|
|||
Ok(response)
|
||||
}
|
||||
|
||||
pub fn edits(&self) -> HashMap<Arc<Path>, FileEdits> {
|
||||
pub fn edits(&self) -> HashMap<Arc<RelPath>, FileEdits> {
|
||||
self.agent_thread
|
||||
.read_with(&self.app, |thread, cx| {
|
||||
let action_log = thread.action_log().read(cx);
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
use std::path::Path;
|
||||
|
||||
use agent_settings::AgentProfileId;
|
||||
use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
use util::rel_path::RelPath;
|
||||
|
||||
use crate::example::{Example, ExampleContext, ExampleMetadata, JudgeAssertion, LanguageServer};
|
||||
|
||||
|
|
@ -68,7 +67,7 @@ impl Example for AddArgToTraitMethod {
|
|||
|
||||
for tool_name in add_ignored_window_paths {
|
||||
let path_str = format!("crates/assistant_tools/src/{}.rs", tool_name);
|
||||
let edits = edits.get(Path::new(&path_str));
|
||||
let edits = edits.get(RelPath::new(&path_str).unwrap());
|
||||
|
||||
let ignored = edits.is_some_and(|edits| {
|
||||
edits.has_added_line(" _window: Option<gpui::AnyWindowHandle>,\n")
|
||||
|
|
@ -86,7 +85,8 @@ impl Example for AddArgToTraitMethod {
|
|||
|
||||
// Adds unignored argument to `batch_tool`
|
||||
|
||||
let batch_tool_edits = edits.get(Path::new("crates/assistant_tools/src/batch_tool.rs"));
|
||||
let batch_tool_edits =
|
||||
edits.get(RelPath::new("crates/assistant_tools/src/batch_tool.rs").unwrap());
|
||||
|
||||
cx.assert(
|
||||
batch_tool_edits.is_some_and(|edits| {
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ impl Example for CodeBlockCitations {
|
|||
thread
|
||||
.project()
|
||||
.read(cx)
|
||||
.find_project_path(path_range.path, cx)
|
||||
.find_project_path(path_range.path.as_ref(), cx)
|
||||
})
|
||||
.ok()
|
||||
.flatten();
|
||||
|
|
|
|||
|
|
@ -250,7 +250,7 @@ impl ExampleInstance {
|
|||
worktree
|
||||
.files(false, 0)
|
||||
.find_map(|e| {
|
||||
if e.path.clone().extension().and_then(|ext| ext.to_str())
|
||||
if e.path.clone().extension()
|
||||
== Some(&language_server.file_extension)
|
||||
{
|
||||
Some(ProjectPath {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ use gpui::{App, Task};
|
|||
use language::LanguageName;
|
||||
use semantic_version::SemanticVersion;
|
||||
use task::{SpawnInTerminal, ZedDebugConfig};
|
||||
use util::rel_path::RelPath;
|
||||
|
||||
pub use crate::capabilities::*;
|
||||
pub use crate::extension_events::*;
|
||||
|
|
@ -33,7 +34,7 @@ pub fn init(cx: &mut App) {
|
|||
pub trait WorktreeDelegate: Send + Sync + 'static {
|
||||
fn id(&self) -> u64;
|
||||
fn root_path(&self) -> String;
|
||||
async fn read_text_file(&self, path: PathBuf) -> Result<String>;
|
||||
async fn read_text_file(&self, path: &RelPath) -> Result<String>;
|
||||
async fn which(&self, binary_name: String) -> Option<String>;
|
||||
async fn shell_env(&self) -> Vec<(String, String)>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1752,7 +1752,14 @@ impl ExtensionStore {
|
|||
})?
|
||||
.await?;
|
||||
let dest_dir = RemotePathBuf::new(
|
||||
PathBuf::from(&response.tmp_dir).join(missing_extension.clone().id),
|
||||
path_style
|
||||
.join(&response.tmp_dir, &missing_extension.id)
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"failed to construct destination path: {:?}, {:?}",
|
||||
response.tmp_dir, missing_extension.id,
|
||||
)
|
||||
})?,
|
||||
path_style,
|
||||
);
|
||||
log::info!("Uploading extension {}", missing_extension.clone().id);
|
||||
|
|
|
|||
|
|
@ -1,10 +1,7 @@
|
|||
use std::{path::PathBuf, sync::Arc};
|
||||
|
||||
use anyhow::{Context as _, Result};
|
||||
use client::{
|
||||
TypedEnvelope,
|
||||
proto::{self, FromProto},
|
||||
};
|
||||
use client::{TypedEnvelope, proto};
|
||||
use collections::{HashMap, HashSet};
|
||||
use extension::{
|
||||
Extension, ExtensionDebugAdapterProviderProxy, ExtensionHostProxy, ExtensionLanguageProxy,
|
||||
|
|
@ -342,7 +339,7 @@ impl HeadlessExtensionStore {
|
|||
version: extension.version,
|
||||
dev: extension.dev,
|
||||
},
|
||||
PathBuf::from_proto(envelope.payload.tmp_dir),
|
||||
PathBuf::from(envelope.payload.tmp_dir),
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ use std::{
|
|||
path::{Path, PathBuf},
|
||||
sync::{Arc, OnceLock},
|
||||
};
|
||||
use util::rel_path::RelPath;
|
||||
use util::{archive::extract_zip, fs::make_file_executable, maybe};
|
||||
use wasmtime::component::{Linker, Resource};
|
||||
|
||||
|
|
@ -421,12 +422,12 @@ impl ExtensionImports for WasmState {
|
|||
) -> wasmtime::Result<Result<String, String>> {
|
||||
self.on_main_thread(|cx| {
|
||||
async move {
|
||||
let location = location
|
||||
.as_ref()
|
||||
.map(|location| ::settings::SettingsLocation {
|
||||
let location = location.as_ref().and_then(|location| {
|
||||
Some(::settings::SettingsLocation {
|
||||
worktree_id: WorktreeId::from_proto(location.worktree_id),
|
||||
path: Path::new(&location.path),
|
||||
});
|
||||
path: RelPath::new(&location.path).ok()?,
|
||||
})
|
||||
});
|
||||
|
||||
cx.update(|cx| match category.as_str() {
|
||||
"language" => {
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ use std::{
|
|||
};
|
||||
use task::{SpawnInTerminal, ZedDebugConfig};
|
||||
use url::Url;
|
||||
use util::{archive::extract_zip, fs::make_file_executable, maybe};
|
||||
use util::{archive::extract_zip, fs::make_file_executable, maybe, rel_path::RelPath};
|
||||
use wasmtime::component::{Linker, Resource};
|
||||
|
||||
pub const MIN_VERSION: SemanticVersion = SemanticVersion::new(0, 6, 0);
|
||||
|
|
@ -564,7 +564,7 @@ impl HostWorktree for WasmState {
|
|||
) -> wasmtime::Result<Result<String, String>> {
|
||||
let delegate = self.table.get(&delegate)?;
|
||||
Ok(delegate
|
||||
.read_text_file(path.into())
|
||||
.read_text_file(RelPath::new(&path)?)
|
||||
.await
|
||||
.map_err(|error| error.to_string()))
|
||||
}
|
||||
|
|
@ -914,12 +914,12 @@ impl ExtensionImports for WasmState {
|
|||
) -> wasmtime::Result<Result<String, String>> {
|
||||
self.on_main_thread(|cx| {
|
||||
async move {
|
||||
let location = location
|
||||
.as_ref()
|
||||
.map(|location| ::settings::SettingsLocation {
|
||||
let location = location.as_ref().and_then(|location| {
|
||||
Some(::settings::SettingsLocation {
|
||||
worktree_id: WorktreeId::from_proto(location.worktree_id),
|
||||
path: Path::new(&location.path),
|
||||
});
|
||||
path: RelPath::new(&location.path).ok()?,
|
||||
})
|
||||
});
|
||||
|
||||
cx.update(|cx| match category.as_str() {
|
||||
"language" => {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
use std::sync::{Arc, OnceLock};
|
||||
|
||||
use db::kvp::KEY_VALUE_STORE;
|
||||
|
|
@ -8,6 +7,7 @@ use extension_host::ExtensionStore;
|
|||
use gpui::{AppContext as _, Context, Entity, SharedString, Window};
|
||||
use language::Buffer;
|
||||
use ui::prelude::*;
|
||||
use util::rel_path::RelPath;
|
||||
use workspace::notifications::simple_message_notification::MessageNotification;
|
||||
use workspace::{Workspace, notifications::NotificationId};
|
||||
|
||||
|
|
@ -100,15 +100,9 @@ struct SuggestedExtension {
|
|||
}
|
||||
|
||||
/// Returns the suggested extension for the given [`Path`].
|
||||
fn suggested_extension(path: impl AsRef<Path>) -> Option<SuggestedExtension> {
|
||||
let path = path.as_ref();
|
||||
|
||||
let file_extension: Option<Arc<str>> = path
|
||||
.extension()
|
||||
.and_then(|extension| Some(extension.to_str()?.into()));
|
||||
let file_name: Option<Arc<str>> = path
|
||||
.file_name()
|
||||
.and_then(|file_name| Some(file_name.to_str()?.into()));
|
||||
fn suggested_extension(path: &RelPath) -> Option<SuggestedExtension> {
|
||||
let file_extension: Option<Arc<str>> = path.extension().map(|extension| extension.into());
|
||||
let file_name: Option<Arc<str>> = path.file_name().map(|name| name.into());
|
||||
|
||||
let (file_name_or_extension, extension_id) = None
|
||||
// We suggest against file names first, as these suggestions will be more
|
||||
|
|
@ -210,39 +204,40 @@ pub(crate) fn suggest(buffer: Entity<Buffer>, window: &mut Window, cx: &mut Cont
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use util::rel_path::rel_path;
|
||||
|
||||
#[test]
|
||||
pub fn test_suggested_extension() {
|
||||
assert_eq!(
|
||||
suggested_extension("Cargo.toml"),
|
||||
suggested_extension(rel_path("Cargo.toml")),
|
||||
Some(SuggestedExtension {
|
||||
extension_id: "toml".into(),
|
||||
file_name_or_extension: "toml".into()
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
suggested_extension("Cargo.lock"),
|
||||
suggested_extension(rel_path("Cargo.lock")),
|
||||
Some(SuggestedExtension {
|
||||
extension_id: "toml".into(),
|
||||
file_name_or_extension: "Cargo.lock".into()
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
suggested_extension("Dockerfile"),
|
||||
suggested_extension(rel_path("Dockerfile")),
|
||||
Some(SuggestedExtension {
|
||||
extension_id: "dockerfile".into(),
|
||||
file_name_or_extension: "Dockerfile".into()
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
suggested_extension("a/b/c/d/.gitignore"),
|
||||
suggested_extension(rel_path("a/b/c/d/.gitignore")),
|
||||
Some(SuggestedExtension {
|
||||
extension_id: "git-firefly".into(),
|
||||
file_name_or_extension: ".gitignore".into()
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
suggested_extension("a/b/c/d/test.gleam"),
|
||||
suggested_extension(rel_path("a/b/c/d/test.gleam")),
|
||||
Some(SuggestedExtension {
|
||||
extension_id: "gleam".into(),
|
||||
file_name_or_extension: "gleam".into()
|
||||
|
|
|
|||
|
|
@ -39,7 +39,12 @@ use ui::{
|
|||
ButtonLike, ContextMenu, HighlightedLabel, Indicator, KeyBinding, ListItem, ListItemSpacing,
|
||||
PopoverMenu, PopoverMenuHandle, TintColor, Tooltip, prelude::*,
|
||||
};
|
||||
use util::{ResultExt, maybe, paths::PathWithPosition, post_inc};
|
||||
use util::{
|
||||
ResultExt, maybe,
|
||||
paths::{PathStyle, PathWithPosition},
|
||||
post_inc,
|
||||
rel_path::RelPath,
|
||||
};
|
||||
use workspace::{
|
||||
ModalView, OpenOptions, OpenVisible, SplitDirection, Workspace, item::PreviewTabsSettings,
|
||||
notifications::NotifyResultExt, pane,
|
||||
|
|
@ -126,38 +131,34 @@ impl FileFinder {
|
|||
let project = workspace.project().read(cx);
|
||||
let fs = project.fs();
|
||||
|
||||
let currently_opened_path = workspace
|
||||
.active_item(cx)
|
||||
.and_then(|item| item.project_path(cx))
|
||||
.map(|project_path| {
|
||||
let abs_path = project
|
||||
.worktree_for_id(project_path.worktree_id, cx)
|
||||
.map(|worktree| worktree.read(cx).abs_path().join(&project_path.path));
|
||||
FoundPath::new(project_path, abs_path)
|
||||
});
|
||||
let currently_opened_path = workspace.active_item(cx).and_then(|item| {
|
||||
let project_path = item.project_path(cx)?;
|
||||
let abs_path = project
|
||||
.worktree_for_id(project_path.worktree_id, cx)?
|
||||
.read(cx)
|
||||
.absolutize(&project_path.path);
|
||||
Some(FoundPath::new(project_path, abs_path))
|
||||
});
|
||||
|
||||
let history_items = workspace
|
||||
.recent_navigation_history(Some(MAX_RECENT_SELECTIONS), cx)
|
||||
.into_iter()
|
||||
.filter_map(|(project_path, abs_path)| {
|
||||
if project.entry_for_path(&project_path, cx).is_some() {
|
||||
return Some(Task::ready(Some(FoundPath::new(project_path, abs_path))));
|
||||
return Some(Task::ready(Some(FoundPath::new(project_path, abs_path?))));
|
||||
}
|
||||
let abs_path = abs_path?;
|
||||
if project.is_local() {
|
||||
let fs = fs.clone();
|
||||
Some(cx.background_spawn(async move {
|
||||
if fs.is_file(&abs_path).await {
|
||||
Some(FoundPath::new(project_path, Some(abs_path)))
|
||||
Some(FoundPath::new(project_path, abs_path))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}))
|
||||
} else {
|
||||
Some(Task::ready(Some(FoundPath::new(
|
||||
project_path,
|
||||
Some(abs_path),
|
||||
))))
|
||||
Some(Task::ready(Some(FoundPath::new(project_path, abs_path))))
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
|
@ -465,7 +466,7 @@ enum Match {
|
|||
}
|
||||
|
||||
impl Match {
|
||||
fn relative_path(&self) -> Option<&Arc<Path>> {
|
||||
fn relative_path(&self) -> Option<&Arc<RelPath>> {
|
||||
match self {
|
||||
Match::History { path, .. } => Some(&path.project.path),
|
||||
Match::Search(panel_match) => Some(&panel_match.0.path),
|
||||
|
|
@ -475,20 +476,14 @@ impl Match {
|
|||
|
||||
fn abs_path(&self, project: &Entity<Project>, cx: &App) -> Option<PathBuf> {
|
||||
match self {
|
||||
Match::History { path, .. } => path.absolute.clone().or_else(|| {
|
||||
Match::History { path, .. } => Some(path.absolute.clone()),
|
||||
Match::Search(ProjectPanelOrdMatch(path_match)) => Some(
|
||||
project
|
||||
.read(cx)
|
||||
.worktree_for_id(path.project.worktree_id, cx)?
|
||||
.worktree_for_id(WorktreeId::from_usize(path_match.worktree_id), cx)?
|
||||
.read(cx)
|
||||
.absolutize(&path.project.path)
|
||||
.ok()
|
||||
}),
|
||||
Match::Search(ProjectPanelOrdMatch(path_match)) => project
|
||||
.read(cx)
|
||||
.worktree_for_id(WorktreeId::from_usize(path_match.worktree_id), cx)?
|
||||
.read(cx)
|
||||
.absolutize(&path_match.path)
|
||||
.ok(),
|
||||
.absolutize(&path_match.path),
|
||||
),
|
||||
Match::CreateNew(_) => None,
|
||||
}
|
||||
}
|
||||
|
|
@ -671,10 +666,9 @@ impl Matches {
|
|||
}
|
||||
|
||||
if let Some(filename) = panel_match.0.path.file_name() {
|
||||
let path_str = panel_match.0.path.to_string_lossy();
|
||||
let filename_str = filename.to_string_lossy();
|
||||
let path_str = panel_match.0.path.as_str();
|
||||
|
||||
if let Some(filename_pos) = path_str.rfind(&*filename_str)
|
||||
if let Some(filename_pos) = path_str.rfind(filename)
|
||||
&& panel_match.0.positions[0] >= filename_pos
|
||||
{
|
||||
let mut prev_position = panel_match.0.positions[0];
|
||||
|
|
@ -696,7 +690,7 @@ fn matching_history_items<'a>(
|
|||
history_items: impl IntoIterator<Item = &'a FoundPath>,
|
||||
currently_opened: Option<&'a FoundPath>,
|
||||
query: &FileSearchQuery,
|
||||
) -> HashMap<Arc<Path>, Match> {
|
||||
) -> HashMap<Arc<RelPath>, Match> {
|
||||
let mut candidates_paths = HashMap::default();
|
||||
|
||||
let history_items_by_worktrees = history_items
|
||||
|
|
@ -714,7 +708,7 @@ fn matching_history_items<'a>(
|
|||
.project
|
||||
.path
|
||||
.file_name()?
|
||||
.to_string_lossy()
|
||||
.to_string()
|
||||
.to_lowercase()
|
||||
.chars(),
|
||||
),
|
||||
|
|
@ -768,11 +762,11 @@ fn matching_history_items<'a>(
|
|||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
struct FoundPath {
|
||||
project: ProjectPath,
|
||||
absolute: Option<PathBuf>,
|
||||
absolute: PathBuf,
|
||||
}
|
||||
|
||||
impl FoundPath {
|
||||
fn new(project: ProjectPath, absolute: Option<PathBuf>) -> Self {
|
||||
fn new(project: ProjectPath, absolute: PathBuf) -> Self {
|
||||
Self { project, absolute }
|
||||
}
|
||||
}
|
||||
|
|
@ -944,47 +938,44 @@ impl FileFinderDelegate {
|
|||
extend_old_matches,
|
||||
);
|
||||
|
||||
let filename = &query.raw_query;
|
||||
let mut query_path = Path::new(filename);
|
||||
// add option of creating new file only if path is relative
|
||||
let available_worktree = self
|
||||
.project
|
||||
.read(cx)
|
||||
.visible_worktrees(cx)
|
||||
.filter(|worktree| !worktree.read(cx).is_single_file())
|
||||
.collect::<Vec<_>>();
|
||||
let worktree_count = available_worktree.len();
|
||||
let mut expect_worktree = available_worktree.first().cloned();
|
||||
for worktree in available_worktree {
|
||||
let worktree_root = worktree
|
||||
let path_style = self.project.read(cx).path_style(cx);
|
||||
let query_path = query.raw_query.as_str();
|
||||
if let Ok(mut query_path) = RelPath::from_std_path(Path::new(query_path), path_style) {
|
||||
let available_worktree = self
|
||||
.project
|
||||
.read(cx)
|
||||
.abs_path()
|
||||
.file_name()
|
||||
.map_or(String::new(), |f| f.to_string_lossy().to_string());
|
||||
if worktree_count > 1 && query_path.starts_with(&worktree_root) {
|
||||
query_path = query_path
|
||||
.strip_prefix(&worktree_root)
|
||||
.unwrap_or(query_path);
|
||||
expect_worktree = Some(worktree);
|
||||
break;
|
||||
.visible_worktrees(cx)
|
||||
.filter(|worktree| !worktree.read(cx).is_single_file())
|
||||
.collect::<Vec<_>>();
|
||||
let worktree_count = available_worktree.len();
|
||||
let mut expect_worktree = available_worktree.first().cloned();
|
||||
for worktree in available_worktree {
|
||||
let worktree_root = worktree.read(cx).root_name();
|
||||
if worktree_count > 1 {
|
||||
if let Ok(suffix) = query_path.strip_prefix(worktree_root) {
|
||||
query_path = suffix.into();
|
||||
expect_worktree = Some(worktree);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(FoundPath { ref project, .. }) = self.currently_opened_path {
|
||||
let worktree_id = project.worktree_id;
|
||||
expect_worktree = self.project.read(cx).worktree_for_id(worktree_id, cx);
|
||||
}
|
||||
if let Some(FoundPath { ref project, .. }) = self.currently_opened_path {
|
||||
let worktree_id = project.worktree_id;
|
||||
expect_worktree = self.project.read(cx).worktree_for_id(worktree_id, cx);
|
||||
}
|
||||
|
||||
if let Some(worktree) = expect_worktree {
|
||||
let worktree = worktree.read(cx);
|
||||
if query_path.is_relative()
|
||||
&& worktree.entry_for_path(&query_path).is_none()
|
||||
&& !filename.ends_with("/")
|
||||
{
|
||||
self.matches.matches.push(Match::CreateNew(ProjectPath {
|
||||
worktree_id: worktree.id(),
|
||||
path: Arc::from(query_path),
|
||||
}));
|
||||
if let Some(worktree) = expect_worktree {
|
||||
let worktree = worktree.read(cx);
|
||||
if worktree.entry_for_path(&query_path).is_none()
|
||||
&& !query.raw_query.ends_with("/")
|
||||
&& !(path_style.is_windows() && query.raw_query.ends_with("\\"))
|
||||
{
|
||||
self.matches.matches.push(Match::CreateNew(ProjectPath {
|
||||
worktree_id: worktree.id(),
|
||||
path: query_path,
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1009,8 +1000,8 @@ impl FileFinderDelegate {
|
|||
path_match: &Match,
|
||||
window: &mut Window,
|
||||
cx: &App,
|
||||
ix: usize,
|
||||
) -> (HighlightedLabel, HighlightedLabel) {
|
||||
let path_style = self.project.read(cx).path_style(cx);
|
||||
let (file_name, file_name_positions, mut full_path, mut full_path_positions) =
|
||||
match &path_match {
|
||||
Match::History {
|
||||
|
|
@ -1018,68 +1009,52 @@ impl FileFinderDelegate {
|
|||
panel_match,
|
||||
} => {
|
||||
let worktree_id = entry_path.project.worktree_id;
|
||||
let project_relative_path = &entry_path.project.path;
|
||||
let has_worktree = self
|
||||
let worktree = self
|
||||
.project
|
||||
.read(cx)
|
||||
.worktree_for_id(worktree_id, cx)
|
||||
.is_some();
|
||||
.filter(|worktree| worktree.read(cx).is_visible());
|
||||
|
||||
if let Some(absolute_path) =
|
||||
entry_path.absolute.as_ref().filter(|_| !has_worktree)
|
||||
{
|
||||
if let Some(panel_match) = panel_match {
|
||||
self.labels_for_path_match(&panel_match.0, path_style)
|
||||
} else if let Some(worktree) = worktree {
|
||||
let full_path =
|
||||
worktree.read(cx).root_name().join(&entry_path.project.path);
|
||||
let mut components = full_path.components();
|
||||
let filename = components.next_back().unwrap_or("");
|
||||
let prefix = components.rest();
|
||||
(
|
||||
absolute_path
|
||||
.file_name()
|
||||
.map_or_else(
|
||||
|| project_relative_path.to_string_lossy(),
|
||||
|file_name| file_name.to_string_lossy(),
|
||||
)
|
||||
.to_string(),
|
||||
filename.to_string(),
|
||||
Vec::new(),
|
||||
absolute_path.to_string_lossy().to_string(),
|
||||
prefix.display(path_style).to_string() + path_style.separator(),
|
||||
Vec::new(),
|
||||
)
|
||||
} else {
|
||||
let mut path = Arc::clone(project_relative_path);
|
||||
if project_relative_path.as_ref() == Path::new("")
|
||||
&& let Some(absolute_path) = &entry_path.absolute
|
||||
{
|
||||
path = Arc::from(absolute_path.as_path());
|
||||
}
|
||||
|
||||
let mut path_match = PathMatch {
|
||||
score: ix as f64,
|
||||
positions: Vec::new(),
|
||||
worktree_id: worktree_id.to_usize(),
|
||||
path,
|
||||
is_dir: false, // File finder doesn't support directories
|
||||
path_prefix: "".into(),
|
||||
distance_to_relative_ancestor: usize::MAX,
|
||||
};
|
||||
if let Some(found_path_match) = &panel_match {
|
||||
path_match
|
||||
.positions
|
||||
.extend(found_path_match.0.positions.iter())
|
||||
}
|
||||
|
||||
self.labels_for_path_match(&path_match)
|
||||
(
|
||||
entry_path
|
||||
.absolute
|
||||
.file_name()
|
||||
.map_or(String::new(), |f| f.to_string_lossy().into_owned()),
|
||||
Vec::new(),
|
||||
entry_path.absolute.parent().map_or(String::new(), |path| {
|
||||
path.to_string_lossy().into_owned() + path_style.separator()
|
||||
}),
|
||||
Vec::new(),
|
||||
)
|
||||
}
|
||||
}
|
||||
Match::Search(path_match) => self.labels_for_path_match(&path_match.0),
|
||||
Match::Search(path_match) => self.labels_for_path_match(&path_match.0, path_style),
|
||||
Match::CreateNew(project_path) => (
|
||||
format!("Create file: {}", project_path.path.display()),
|
||||
format!("Create file: {}", project_path.path.display(path_style)),
|
||||
vec![],
|
||||
String::from(""),
|
||||
vec![],
|
||||
),
|
||||
};
|
||||
|
||||
if file_name_positions.is_empty()
|
||||
&& let Some(user_home_path) = std::env::var("HOME").ok()
|
||||
{
|
||||
let user_home_path = user_home_path.trim();
|
||||
if !user_home_path.is_empty() && full_path.starts_with(user_home_path) {
|
||||
if file_name_positions.is_empty() {
|
||||
let user_home_path = util::paths::home_dir().to_string_lossy();
|
||||
if !user_home_path.is_empty() && full_path.starts_with(&*user_home_path) {
|
||||
full_path.replace_range(0..user_home_path.len(), "~");
|
||||
full_path_positions.retain_mut(|pos| {
|
||||
if *pos >= user_home_path.len() {
|
||||
|
|
@ -1147,17 +1122,13 @@ impl FileFinderDelegate {
|
|||
fn labels_for_path_match(
|
||||
&self,
|
||||
path_match: &PathMatch,
|
||||
path_style: PathStyle,
|
||||
) -> (String, Vec<usize>, String, Vec<usize>) {
|
||||
let path = &path_match.path;
|
||||
let path_string = path.to_string_lossy();
|
||||
let full_path = [path_match.path_prefix.as_ref(), path_string.as_ref()].join("");
|
||||
let full_path = path_match.path_prefix.join(&path_match.path);
|
||||
let mut path_positions = path_match.positions.clone();
|
||||
|
||||
let file_name = path.file_name().map_or_else(
|
||||
|| path_match.path_prefix.to_string(),
|
||||
|file_name| file_name.to_string_lossy().to_string(),
|
||||
);
|
||||
let file_name_start = path_match.path_prefix.len() + path_string.len() - file_name.len();
|
||||
let file_name = full_path.file_name().unwrap_or("");
|
||||
let file_name_start = full_path.as_str().len() - file_name.len();
|
||||
let file_name_positions = path_positions
|
||||
.iter()
|
||||
.filter_map(|pos| {
|
||||
|
|
@ -1167,12 +1138,33 @@ impl FileFinderDelegate {
|
|||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let full_path = full_path.trim_end_matches(&file_name).to_string();
|
||||
let full_path = full_path
|
||||
.display(path_style)
|
||||
.trim_end_matches(&file_name)
|
||||
.to_string();
|
||||
path_positions.retain(|idx| *idx < full_path.len());
|
||||
|
||||
(file_name, file_name_positions, full_path, path_positions)
|
||||
debug_assert!(
|
||||
file_name_positions
|
||||
.iter()
|
||||
.all(|ix| file_name[*ix..].chars().next().is_some()),
|
||||
"invalid file name positions {file_name:?} {file_name_positions:?}"
|
||||
);
|
||||
debug_assert!(
|
||||
path_positions
|
||||
.iter()
|
||||
.all(|ix| full_path[*ix..].chars().next().is_some()),
|
||||
"invalid path positions {full_path:?} {path_positions:?}"
|
||||
);
|
||||
|
||||
(
|
||||
file_name.to_string(),
|
||||
file_name_positions,
|
||||
full_path,
|
||||
path_positions,
|
||||
)
|
||||
}
|
||||
|
||||
fn lookup_absolute_path(
|
||||
|
|
@ -1210,8 +1202,8 @@ impl FileFinderDelegate {
|
|||
score: 1.0,
|
||||
positions: Vec::new(),
|
||||
worktree_id: worktree.read(cx).id().to_usize(),
|
||||
path: Arc::from(relative_path),
|
||||
path_prefix: "".into(),
|
||||
path: relative_path,
|
||||
path_prefix: RelPath::empty().into(),
|
||||
is_dir: false, // File finder doesn't support directories
|
||||
distance_to_relative_ancestor: usize::MAX,
|
||||
}));
|
||||
|
|
@ -1333,7 +1325,7 @@ impl PickerDelegate for FileFinderDelegate {
|
|||
.all(|worktree| {
|
||||
worktree
|
||||
.read(cx)
|
||||
.entry_for_path(Path::new("a"))
|
||||
.entry_for_path(RelPath::new("a").unwrap())
|
||||
.is_none_or(|entry| !entry.is_dir())
|
||||
})
|
||||
{
|
||||
|
|
@ -1351,7 +1343,7 @@ impl PickerDelegate for FileFinderDelegate {
|
|||
.all(|worktree| {
|
||||
worktree
|
||||
.read(cx)
|
||||
.entry_for_path(Path::new("b"))
|
||||
.entry_for_path(RelPath::new("b").unwrap())
|
||||
.is_none_or(|entry| !entry.is_dir())
|
||||
})
|
||||
{
|
||||
|
|
@ -1381,8 +1373,8 @@ impl PickerDelegate for FileFinderDelegate {
|
|||
project
|
||||
.worktree_for_id(history_item.project.worktree_id, cx)
|
||||
.is_some()
|
||||
|| ((project.is_local() || project.is_via_remote_server())
|
||||
&& history_item.absolute.is_some())
|
||||
|| project.is_local()
|
||||
|| project.is_via_remote_server()
|
||||
}),
|
||||
self.currently_opened_path.as_ref(),
|
||||
None,
|
||||
|
|
@ -1397,13 +1389,7 @@ impl PickerDelegate for FileFinderDelegate {
|
|||
Task::ready(())
|
||||
} else {
|
||||
let path_position = PathWithPosition::parse_str(raw_query);
|
||||
|
||||
#[cfg(windows)]
|
||||
let raw_query = raw_query.trim().to_owned().replace("/", "\\");
|
||||
#[cfg(not(windows))]
|
||||
let raw_query = raw_query.trim();
|
||||
|
||||
let raw_query = raw_query.trim_end_matches(':').to_owned();
|
||||
let raw_query = raw_query.trim().trim_end_matches(':').to_owned();
|
||||
let path = path_position.path.to_str();
|
||||
let path_trimmed = path.unwrap_or(&raw_query).trim_end_matches(':');
|
||||
let file_query_end = if path_trimmed == raw_query {
|
||||
|
|
@ -1505,38 +1491,18 @@ impl PickerDelegate for FileFinderDelegate {
|
|||
window,
|
||||
cx,
|
||||
)
|
||||
} else if secondary {
|
||||
workspace.split_abs_path(path.absolute.clone(), false, window, cx)
|
||||
} else {
|
||||
match path.absolute.as_ref() {
|
||||
Some(abs_path) => {
|
||||
if secondary {
|
||||
workspace.split_abs_path(
|
||||
abs_path.to_path_buf(),
|
||||
false,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
} else {
|
||||
workspace.open_abs_path(
|
||||
abs_path.to_path_buf(),
|
||||
OpenOptions {
|
||||
visible: Some(OpenVisible::None),
|
||||
..Default::default()
|
||||
},
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
}
|
||||
None => split_or_open(
|
||||
workspace,
|
||||
ProjectPath {
|
||||
worktree_id,
|
||||
path: Arc::clone(&path.project.path),
|
||||
},
|
||||
window,
|
||||
cx,
|
||||
),
|
||||
}
|
||||
workspace.open_abs_path(
|
||||
path.absolute.clone(),
|
||||
OpenOptions {
|
||||
visible: Some(OpenVisible::None),
|
||||
..Default::default()
|
||||
},
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
}
|
||||
Match::Search(m) => split_or_open(
|
||||
|
|
@ -1615,7 +1581,7 @@ impl PickerDelegate for FileFinderDelegate {
|
|||
.size(IconSize::Small)
|
||||
.into_any_element(),
|
||||
};
|
||||
let (file_name_label, full_path_label) = self.labels_for_match(path_match, window, cx, ix);
|
||||
let (file_name_label, full_path_label) = self.labels_for_match(path_match, window, cx);
|
||||
|
||||
let file_icon = maybe!({
|
||||
if !settings.file_icons {
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@ use super::*;
|
|||
use editor::Editor;
|
||||
use gpui::{Entity, TestAppContext, VisualTestContext};
|
||||
use menu::{Confirm, SelectNext, SelectPrevious};
|
||||
use pretty_assertions::assert_eq;
|
||||
use pretty_assertions::{assert_eq, assert_matches};
|
||||
use project::{FS_WATCH_LATENCY, RemoveOptions};
|
||||
use serde_json::json;
|
||||
use util::path;
|
||||
use util::{path, rel_path::rel_path};
|
||||
use workspace::{AppState, CloseActiveItem, OpenOptions, ToggleFileFinder, Workspace};
|
||||
|
||||
#[ctor::ctor]
|
||||
|
|
@ -77,8 +77,8 @@ fn test_custom_project_search_ordering_in_file_finder() {
|
|||
score: 0.5,
|
||||
positions: Vec::new(),
|
||||
worktree_id: 0,
|
||||
path: Arc::from(Path::new("b0.5")),
|
||||
path_prefix: Arc::default(),
|
||||
path: rel_path("b0.5").into(),
|
||||
path_prefix: rel_path("").into(),
|
||||
distance_to_relative_ancestor: 0,
|
||||
is_dir: false,
|
||||
}),
|
||||
|
|
@ -86,8 +86,8 @@ fn test_custom_project_search_ordering_in_file_finder() {
|
|||
score: 1.0,
|
||||
positions: Vec::new(),
|
||||
worktree_id: 0,
|
||||
path: Arc::from(Path::new("c1.0")),
|
||||
path_prefix: Arc::default(),
|
||||
path: rel_path("c1.0").into(),
|
||||
path_prefix: rel_path("").into(),
|
||||
distance_to_relative_ancestor: 0,
|
||||
is_dir: false,
|
||||
}),
|
||||
|
|
@ -95,8 +95,8 @@ fn test_custom_project_search_ordering_in_file_finder() {
|
|||
score: 1.0,
|
||||
positions: Vec::new(),
|
||||
worktree_id: 0,
|
||||
path: Arc::from(Path::new("a1.0")),
|
||||
path_prefix: Arc::default(),
|
||||
path: rel_path("a1.0").into(),
|
||||
path_prefix: rel_path("").into(),
|
||||
distance_to_relative_ancestor: 0,
|
||||
is_dir: false,
|
||||
}),
|
||||
|
|
@ -104,8 +104,8 @@ fn test_custom_project_search_ordering_in_file_finder() {
|
|||
score: 0.5,
|
||||
positions: Vec::new(),
|
||||
worktree_id: 0,
|
||||
path: Arc::from(Path::new("a0.5")),
|
||||
path_prefix: Arc::default(),
|
||||
path: rel_path("a0.5").into(),
|
||||
path_prefix: rel_path("").into(),
|
||||
distance_to_relative_ancestor: 0,
|
||||
is_dir: false,
|
||||
}),
|
||||
|
|
@ -113,8 +113,8 @@ fn test_custom_project_search_ordering_in_file_finder() {
|
|||
score: 1.0,
|
||||
positions: Vec::new(),
|
||||
worktree_id: 0,
|
||||
path: Arc::from(Path::new("b1.0")),
|
||||
path_prefix: Arc::default(),
|
||||
path: rel_path("b1.0").into(),
|
||||
path_prefix: rel_path("").into(),
|
||||
distance_to_relative_ancestor: 0,
|
||||
is_dir: false,
|
||||
}),
|
||||
|
|
@ -128,8 +128,8 @@ fn test_custom_project_search_ordering_in_file_finder() {
|
|||
score: 1.0,
|
||||
positions: Vec::new(),
|
||||
worktree_id: 0,
|
||||
path: Arc::from(Path::new("a1.0")),
|
||||
path_prefix: Arc::default(),
|
||||
path: rel_path("a1.0").into(),
|
||||
path_prefix: rel_path("").into(),
|
||||
distance_to_relative_ancestor: 0,
|
||||
is_dir: false,
|
||||
}),
|
||||
|
|
@ -137,8 +137,8 @@ fn test_custom_project_search_ordering_in_file_finder() {
|
|||
score: 1.0,
|
||||
positions: Vec::new(),
|
||||
worktree_id: 0,
|
||||
path: Arc::from(Path::new("b1.0")),
|
||||
path_prefix: Arc::default(),
|
||||
path: rel_path("b1.0").into(),
|
||||
path_prefix: rel_path("").into(),
|
||||
distance_to_relative_ancestor: 0,
|
||||
is_dir: false,
|
||||
}),
|
||||
|
|
@ -146,8 +146,8 @@ fn test_custom_project_search_ordering_in_file_finder() {
|
|||
score: 1.0,
|
||||
positions: Vec::new(),
|
||||
worktree_id: 0,
|
||||
path: Arc::from(Path::new("c1.0")),
|
||||
path_prefix: Arc::default(),
|
||||
path: rel_path("c1.0").into(),
|
||||
path_prefix: rel_path("").into(),
|
||||
distance_to_relative_ancestor: 0,
|
||||
is_dir: false,
|
||||
}),
|
||||
|
|
@ -155,8 +155,8 @@ fn test_custom_project_search_ordering_in_file_finder() {
|
|||
score: 0.5,
|
||||
positions: Vec::new(),
|
||||
worktree_id: 0,
|
||||
path: Arc::from(Path::new("a0.5")),
|
||||
path_prefix: Arc::default(),
|
||||
path: rel_path("a0.5").into(),
|
||||
path_prefix: rel_path("").into(),
|
||||
distance_to_relative_ancestor: 0,
|
||||
is_dir: false,
|
||||
}),
|
||||
|
|
@ -164,8 +164,8 @@ fn test_custom_project_search_ordering_in_file_finder() {
|
|||
score: 0.5,
|
||||
positions: Vec::new(),
|
||||
worktree_id: 0,
|
||||
path: Arc::from(Path::new("b0.5")),
|
||||
path_prefix: Arc::default(),
|
||||
path: rel_path("b0.5").into(),
|
||||
path_prefix: rel_path("").into(),
|
||||
distance_to_relative_ancestor: 0,
|
||||
is_dir: false,
|
||||
}),
|
||||
|
|
@ -366,7 +366,7 @@ async fn test_absolute_paths(cx: &mut TestAppContext) {
|
|||
picker.update(cx, |picker, _| {
|
||||
assert_eq!(
|
||||
collect_search_matches(picker).search_paths_only(),
|
||||
vec![PathBuf::from("a/b/file2.txt")],
|
||||
vec![rel_path("a/b/file2.txt").into()],
|
||||
"Matching abs path should be the only match"
|
||||
)
|
||||
});
|
||||
|
|
@ -388,7 +388,7 @@ async fn test_absolute_paths(cx: &mut TestAppContext) {
|
|||
picker.update(cx, |picker, _| {
|
||||
assert_eq!(
|
||||
collect_search_matches(picker).search_paths_only(),
|
||||
Vec::<PathBuf>::new(),
|
||||
Vec::new(),
|
||||
"Mismatching abs path should produce no matches"
|
||||
)
|
||||
});
|
||||
|
|
@ -421,7 +421,7 @@ async fn test_complex_path(cx: &mut TestAppContext) {
|
|||
assert_eq!(picker.delegate.matches.len(), 2);
|
||||
assert_eq!(
|
||||
collect_search_matches(picker).search_paths_only(),
|
||||
vec![PathBuf::from("其他/S数据表格/task.xlsx")],
|
||||
vec![rel_path("其他/S数据表格/task.xlsx").into()],
|
||||
)
|
||||
});
|
||||
cx.dispatch_action(Confirm);
|
||||
|
|
@ -713,13 +713,13 @@ async fn test_ignored_root(cx: &mut TestAppContext) {
|
|||
assert_eq!(
|
||||
matches.search,
|
||||
vec![
|
||||
PathBuf::from("ignored-root/hi"),
|
||||
PathBuf::from("tracked-root/hi"),
|
||||
PathBuf::from("ignored-root/hiccup"),
|
||||
PathBuf::from("tracked-root/hiccup"),
|
||||
PathBuf::from("ignored-root/height"),
|
||||
PathBuf::from("ignored-root/happiness"),
|
||||
PathBuf::from("tracked-root/happiness"),
|
||||
rel_path("ignored-root/hi").into(),
|
||||
rel_path("tracked-root/hi").into(),
|
||||
rel_path("ignored-root/hiccup").into(),
|
||||
rel_path("tracked-root/hiccup").into(),
|
||||
rel_path("ignored-root/height").into(),
|
||||
rel_path("ignored-root/happiness").into(),
|
||||
rel_path("tracked-root/happiness").into(),
|
||||
],
|
||||
"All ignored files that were indexed are found for default ignored mode"
|
||||
);
|
||||
|
|
@ -738,14 +738,14 @@ async fn test_ignored_root(cx: &mut TestAppContext) {
|
|||
assert_eq!(
|
||||
matches.search,
|
||||
vec![
|
||||
PathBuf::from("ignored-root/hi"),
|
||||
PathBuf::from("tracked-root/hi"),
|
||||
PathBuf::from("ignored-root/hiccup"),
|
||||
PathBuf::from("tracked-root/hiccup"),
|
||||
PathBuf::from("ignored-root/height"),
|
||||
PathBuf::from("tracked-root/height"),
|
||||
PathBuf::from("ignored-root/happiness"),
|
||||
PathBuf::from("tracked-root/happiness"),
|
||||
rel_path("ignored-root/hi").into(),
|
||||
rel_path("tracked-root/hi").into(),
|
||||
rel_path("ignored-root/hiccup").into(),
|
||||
rel_path("tracked-root/hiccup").into(),
|
||||
rel_path("ignored-root/height").into(),
|
||||
rel_path("tracked-root/height").into(),
|
||||
rel_path("ignored-root/happiness").into(),
|
||||
rel_path("tracked-root/happiness").into(),
|
||||
],
|
||||
"All ignored files should be found, for the toggled on ignored mode"
|
||||
);
|
||||
|
|
@ -765,9 +765,9 @@ async fn test_ignored_root(cx: &mut TestAppContext) {
|
|||
assert_eq!(
|
||||
matches.search,
|
||||
vec![
|
||||
PathBuf::from("tracked-root/hi"),
|
||||
PathBuf::from("tracked-root/hiccup"),
|
||||
PathBuf::from("tracked-root/happiness"),
|
||||
rel_path("tracked-root/hi").into(),
|
||||
rel_path("tracked-root/hiccup").into(),
|
||||
rel_path("tracked-root/happiness").into(),
|
||||
],
|
||||
"Only non-ignored files should be found for the turned off ignored mode"
|
||||
);
|
||||
|
|
@ -812,13 +812,13 @@ async fn test_ignored_root(cx: &mut TestAppContext) {
|
|||
assert_eq!(
|
||||
matches.search,
|
||||
vec![
|
||||
PathBuf::from("ignored-root/hi"),
|
||||
PathBuf::from("tracked-root/hi"),
|
||||
PathBuf::from("ignored-root/hiccup"),
|
||||
PathBuf::from("tracked-root/hiccup"),
|
||||
PathBuf::from("ignored-root/height"),
|
||||
PathBuf::from("ignored-root/happiness"),
|
||||
PathBuf::from("tracked-root/happiness"),
|
||||
rel_path("ignored-root/hi").into(),
|
||||
rel_path("tracked-root/hi").into(),
|
||||
rel_path("ignored-root/hiccup").into(),
|
||||
rel_path("tracked-root/hiccup").into(),
|
||||
rel_path("ignored-root/height").into(),
|
||||
rel_path("ignored-root/happiness").into(),
|
||||
rel_path("tracked-root/happiness").into(),
|
||||
],
|
||||
"Only for the worktree with the ignored root, all indexed ignored files are found in the auto ignored mode"
|
||||
);
|
||||
|
|
@ -838,16 +838,16 @@ async fn test_ignored_root(cx: &mut TestAppContext) {
|
|||
assert_eq!(
|
||||
matches.search,
|
||||
vec![
|
||||
PathBuf::from("ignored-root/hi"),
|
||||
PathBuf::from("tracked-root/hi"),
|
||||
PathBuf::from("ignored-root/hiccup"),
|
||||
PathBuf::from("tracked-root/hiccup"),
|
||||
PathBuf::from("ignored-root/height"),
|
||||
PathBuf::from("tracked-root/height"),
|
||||
PathBuf::from("tracked-root/heights/height_1"),
|
||||
PathBuf::from("tracked-root/heights/height_2"),
|
||||
PathBuf::from("ignored-root/happiness"),
|
||||
PathBuf::from("tracked-root/happiness"),
|
||||
rel_path("ignored-root/hi").into(),
|
||||
rel_path("tracked-root/hi").into(),
|
||||
rel_path("ignored-root/hiccup").into(),
|
||||
rel_path("tracked-root/hiccup").into(),
|
||||
rel_path("ignored-root/height").into(),
|
||||
rel_path("tracked-root/height").into(),
|
||||
rel_path("tracked-root/heights/height_1").into(),
|
||||
rel_path("tracked-root/heights/height_2").into(),
|
||||
rel_path("ignored-root/happiness").into(),
|
||||
rel_path("tracked-root/happiness").into(),
|
||||
],
|
||||
"All ignored files that were indexed are found in the turned on ignored mode"
|
||||
);
|
||||
|
|
@ -867,9 +867,9 @@ async fn test_ignored_root(cx: &mut TestAppContext) {
|
|||
assert_eq!(
|
||||
matches.search,
|
||||
vec![
|
||||
PathBuf::from("tracked-root/hi"),
|
||||
PathBuf::from("tracked-root/hiccup"),
|
||||
PathBuf::from("tracked-root/happiness"),
|
||||
rel_path("tracked-root/hi").into(),
|
||||
rel_path("tracked-root/hiccup").into(),
|
||||
rel_path("tracked-root/happiness").into(),
|
||||
],
|
||||
"Only non-ignored files should be found for the turned off ignored mode"
|
||||
);
|
||||
|
|
@ -910,7 +910,7 @@ async fn test_single_file_worktrees(cx: &mut TestAppContext) {
|
|||
assert_eq!(matches.len(), 1);
|
||||
|
||||
let (file_name, file_name_positions, full_path, full_path_positions) =
|
||||
delegate.labels_for_path_match(&matches[0]);
|
||||
delegate.labels_for_path_match(&matches[0], PathStyle::local());
|
||||
assert_eq!(file_name, "the-file");
|
||||
assert_eq!(file_name_positions, &[0, 1, 4]);
|
||||
assert_eq!(full_path, "");
|
||||
|
|
@ -968,7 +968,7 @@ async fn test_create_file_for_multiple_worktrees(cx: &mut TestAppContext) {
|
|||
|
||||
let b_path = ProjectPath {
|
||||
worktree_id: worktree_id2,
|
||||
path: Arc::from(Path::new(path!("the-parent-dirb/fileb"))),
|
||||
path: rel_path("the-parent-dirb/fileb").into(),
|
||||
};
|
||||
workspace
|
||||
.update_in(cx, |workspace, window, cx| {
|
||||
|
|
@ -1001,7 +1001,7 @@ async fn test_create_file_for_multiple_worktrees(cx: &mut TestAppContext) {
|
|||
project_path,
|
||||
Some(ProjectPath {
|
||||
worktree_id: worktree_id2,
|
||||
path: Arc::from(Path::new(path!("the-parent-dirb/filec")))
|
||||
path: rel_path("the-parent-dirb/filec").into()
|
||||
})
|
||||
);
|
||||
});
|
||||
|
|
@ -1038,10 +1038,7 @@ async fn test_create_file_no_focused_with_multiple_worktrees(cx: &mut TestAppCon
|
|||
let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
|
||||
let (_worktree_id1, worktree_id2) = cx.read(|cx| {
|
||||
let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
|
||||
(
|
||||
WorktreeId::from_usize(worktrees[0].entity_id().as_u64() as usize),
|
||||
WorktreeId::from_usize(worktrees[1].entity_id().as_u64() as usize),
|
||||
)
|
||||
(worktrees[0].read(cx).id(), worktrees[1].read(cx).id())
|
||||
});
|
||||
|
||||
let finder = open_file_picker(&workspace, cx);
|
||||
|
|
@ -1065,7 +1062,7 @@ async fn test_create_file_no_focused_with_multiple_worktrees(cx: &mut TestAppCon
|
|||
project_path,
|
||||
Some(ProjectPath {
|
||||
worktree_id: worktree_id2,
|
||||
path: Arc::from(Path::new("filec"))
|
||||
path: rel_path("filec").into()
|
||||
})
|
||||
);
|
||||
});
|
||||
|
|
@ -1103,7 +1100,7 @@ async fn test_path_distance_ordering(cx: &mut TestAppContext) {
|
|||
// so that one should be sorted earlier
|
||||
let b_path = ProjectPath {
|
||||
worktree_id,
|
||||
path: Arc::from(Path::new("dir2/b.txt")),
|
||||
path: rel_path("dir2/b.txt").into(),
|
||||
};
|
||||
workspace
|
||||
.update_in(cx, |workspace, window, cx| {
|
||||
|
|
@ -1121,8 +1118,8 @@ async fn test_path_distance_ordering(cx: &mut TestAppContext) {
|
|||
|
||||
finder.update(cx, |picker, _| {
|
||||
let matches = collect_search_matches(picker).search_paths_only();
|
||||
assert_eq!(matches[0].as_path(), Path::new("dir2/a.txt"));
|
||||
assert_eq!(matches[1].as_path(), Path::new("dir1/a.txt"));
|
||||
assert_eq!(matches[0].as_ref(), rel_path("dir2/a.txt"));
|
||||
assert_eq!(matches[1].as_ref(), rel_path("dir1/a.txt"));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -1207,9 +1204,9 @@ async fn test_query_history(cx: &mut gpui::TestAppContext) {
|
|||
vec![FoundPath::new(
|
||||
ProjectPath {
|
||||
worktree_id,
|
||||
path: Arc::from(Path::new("test/first.rs")),
|
||||
path: rel_path("test/first.rs").into(),
|
||||
},
|
||||
Some(PathBuf::from(path!("/src/test/first.rs")))
|
||||
PathBuf::from(path!("/src/test/first.rs"))
|
||||
)],
|
||||
"Should show 1st opened item in the history when opening the 2nd item"
|
||||
);
|
||||
|
|
@ -1222,16 +1219,16 @@ async fn test_query_history(cx: &mut gpui::TestAppContext) {
|
|||
FoundPath::new(
|
||||
ProjectPath {
|
||||
worktree_id,
|
||||
path: Arc::from(Path::new("test/second.rs")),
|
||||
path: rel_path("test/second.rs").into(),
|
||||
},
|
||||
Some(PathBuf::from(path!("/src/test/second.rs")))
|
||||
PathBuf::from(path!("/src/test/second.rs"))
|
||||
),
|
||||
FoundPath::new(
|
||||
ProjectPath {
|
||||
worktree_id,
|
||||
path: Arc::from(Path::new("test/first.rs")),
|
||||
path: rel_path("test/first.rs").into(),
|
||||
},
|
||||
Some(PathBuf::from(path!("/src/test/first.rs")))
|
||||
PathBuf::from(path!("/src/test/first.rs"))
|
||||
),
|
||||
],
|
||||
"Should show 1st and 2nd opened items in the history when opening the 3rd item. \
|
||||
|
|
@ -1246,23 +1243,23 @@ async fn test_query_history(cx: &mut gpui::TestAppContext) {
|
|||
FoundPath::new(
|
||||
ProjectPath {
|
||||
worktree_id,
|
||||
path: Arc::from(Path::new("test/third.rs")),
|
||||
path: rel_path("test/third.rs").into(),
|
||||
},
|
||||
Some(PathBuf::from(path!("/src/test/third.rs")))
|
||||
PathBuf::from(path!("/src/test/third.rs"))
|
||||
),
|
||||
FoundPath::new(
|
||||
ProjectPath {
|
||||
worktree_id,
|
||||
path: Arc::from(Path::new("test/second.rs")),
|
||||
path: rel_path("test/second.rs").into(),
|
||||
},
|
||||
Some(PathBuf::from(path!("/src/test/second.rs")))
|
||||
PathBuf::from(path!("/src/test/second.rs"))
|
||||
),
|
||||
FoundPath::new(
|
||||
ProjectPath {
|
||||
worktree_id,
|
||||
path: Arc::from(Path::new("test/first.rs")),
|
||||
path: rel_path("test/first.rs").into(),
|
||||
},
|
||||
Some(PathBuf::from(path!("/src/test/first.rs")))
|
||||
PathBuf::from(path!("/src/test/first.rs"))
|
||||
),
|
||||
],
|
||||
"Should show 1st, 2nd and 3rd opened items in the history when opening the 2nd item again. \
|
||||
|
|
@ -1277,23 +1274,23 @@ async fn test_query_history(cx: &mut gpui::TestAppContext) {
|
|||
FoundPath::new(
|
||||
ProjectPath {
|
||||
worktree_id,
|
||||
path: Arc::from(Path::new("test/second.rs")),
|
||||
path: rel_path("test/second.rs").into(),
|
||||
},
|
||||
Some(PathBuf::from(path!("/src/test/second.rs")))
|
||||
PathBuf::from(path!("/src/test/second.rs"))
|
||||
),
|
||||
FoundPath::new(
|
||||
ProjectPath {
|
||||
worktree_id,
|
||||
path: Arc::from(Path::new("test/third.rs")),
|
||||
path: rel_path("test/third.rs").into(),
|
||||
},
|
||||
Some(PathBuf::from(path!("/src/test/third.rs")))
|
||||
PathBuf::from(path!("/src/test/third.rs"))
|
||||
),
|
||||
FoundPath::new(
|
||||
ProjectPath {
|
||||
worktree_id,
|
||||
path: Arc::from(Path::new("test/first.rs")),
|
||||
path: rel_path("test/first.rs").into(),
|
||||
},
|
||||
Some(PathBuf::from(path!("/src/test/first.rs")))
|
||||
PathBuf::from(path!("/src/test/first.rs"))
|
||||
),
|
||||
],
|
||||
"Should show 1st, 2nd and 3rd opened items in the history when opening the 3rd item again. \
|
||||
|
|
@ -1301,6 +1298,62 @@ async fn test_query_history(cx: &mut gpui::TestAppContext) {
|
|||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_history_match_positions(cx: &mut gpui::TestAppContext) {
|
||||
let app_state = init_test(cx);
|
||||
|
||||
app_state
|
||||
.fs
|
||||
.as_fake()
|
||||
.insert_tree(
|
||||
path!("/src"),
|
||||
json!({
|
||||
"test": {
|
||||
"first.rs": "// First Rust file",
|
||||
"second.rs": "// Second Rust file",
|
||||
"third.rs": "// Third Rust file",
|
||||
}
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
let project = Project::test(app_state.fs.clone(), [path!("/src").as_ref()], cx).await;
|
||||
let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
|
||||
|
||||
workspace.update_in(cx, |_workspace, window, cx| window.focused(cx));
|
||||
|
||||
open_close_queried_buffer("efir", 1, "first.rs", &workspace, cx).await;
|
||||
let history = open_close_queried_buffer("second", 1, "second.rs", &workspace, cx).await;
|
||||
assert_eq!(history.len(), 1);
|
||||
|
||||
let picker = open_file_picker(&workspace, cx);
|
||||
cx.simulate_input("fir");
|
||||
picker.update_in(cx, |finder, window, cx| {
|
||||
let matches = &finder.delegate.matches.matches;
|
||||
assert_matches!(
|
||||
matches.as_slice(),
|
||||
[Match::History { .. }, Match::CreateNew { .. }]
|
||||
);
|
||||
assert_eq!(
|
||||
matches[0].panel_match().unwrap().0.path.as_ref(),
|
||||
rel_path("test/first.rs")
|
||||
);
|
||||
assert_eq!(matches[0].panel_match().unwrap().0.positions, &[5, 6, 7]);
|
||||
|
||||
let (file_label, path_label) =
|
||||
finder
|
||||
.delegate
|
||||
.labels_for_match(&finder.delegate.matches.matches[0], window, cx);
|
||||
assert_eq!(file_label.text(), "first.rs");
|
||||
assert_eq!(file_label.highlight_indices(), &[0, 1, 2]);
|
||||
assert_eq!(
|
||||
path_label.text(),
|
||||
format!("test{}", PathStyle::local().separator())
|
||||
);
|
||||
assert_eq!(path_label.highlight_indices(), &[] as &[usize]);
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_external_files_history(cx: &mut gpui::TestAppContext) {
|
||||
let app_state = init_test(cx);
|
||||
|
|
@ -1392,9 +1445,9 @@ async fn test_external_files_history(cx: &mut gpui::TestAppContext) {
|
|||
vec![FoundPath::new(
|
||||
ProjectPath {
|
||||
worktree_id: external_worktree_id,
|
||||
path: Arc::from(Path::new("")),
|
||||
path: rel_path("").into(),
|
||||
},
|
||||
Some(PathBuf::from(path!("/external-src/test/third.rs")))
|
||||
PathBuf::from(path!("/external-src/test/third.rs"))
|
||||
)],
|
||||
"Should show external file with its full path in the history after it was open"
|
||||
);
|
||||
|
|
@ -1407,16 +1460,16 @@ async fn test_external_files_history(cx: &mut gpui::TestAppContext) {
|
|||
FoundPath::new(
|
||||
ProjectPath {
|
||||
worktree_id,
|
||||
path: Arc::from(Path::new("test/second.rs")),
|
||||
path: rel_path("test/second.rs").into(),
|
||||
},
|
||||
Some(PathBuf::from(path!("/src/test/second.rs")))
|
||||
PathBuf::from(path!("/src/test/second.rs"))
|
||||
),
|
||||
FoundPath::new(
|
||||
ProjectPath {
|
||||
worktree_id: external_worktree_id,
|
||||
path: Arc::from(Path::new("")),
|
||||
path: rel_path("").into(),
|
||||
},
|
||||
Some(PathBuf::from(path!("/external-src/test/third.rs")))
|
||||
PathBuf::from(path!("/external-src/test/third.rs"))
|
||||
),
|
||||
],
|
||||
"Should keep external file with history updates",
|
||||
|
|
@ -1529,12 +1582,12 @@ async fn test_search_preserves_history_items(cx: &mut gpui::TestAppContext) {
|
|||
assert_eq!(history_match, &FoundPath::new(
|
||||
ProjectPath {
|
||||
worktree_id,
|
||||
path: Arc::from(Path::new("test/first.rs")),
|
||||
path: rel_path("test/first.rs").into(),
|
||||
},
|
||||
Some(PathBuf::from(path!("/src/test/first.rs")))
|
||||
PathBuf::from(path!("/src/test/first.rs")),
|
||||
));
|
||||
assert_eq!(matches.search.len(), 1, "Only one non-history item contains {first_query}, it should be present");
|
||||
assert_eq!(matches.search.first().unwrap(), Path::new("test/fourth.rs"));
|
||||
assert_eq!(matches.search.first().unwrap().as_ref(), rel_path("test/fourth.rs"));
|
||||
});
|
||||
|
||||
let second_query = "fsdasdsa";
|
||||
|
|
@ -1572,12 +1625,12 @@ async fn test_search_preserves_history_items(cx: &mut gpui::TestAppContext) {
|
|||
assert_eq!(history_match, &FoundPath::new(
|
||||
ProjectPath {
|
||||
worktree_id,
|
||||
path: Arc::from(Path::new("test/first.rs")),
|
||||
path: rel_path("test/first.rs").into(),
|
||||
},
|
||||
Some(PathBuf::from(path!("/src/test/first.rs")))
|
||||
PathBuf::from(path!("/src/test/first.rs"))
|
||||
));
|
||||
assert_eq!(matches.search.len(), 1, "Only one non-history item contains {first_query_again}, it should be present, even after non-matching query");
|
||||
assert_eq!(matches.search.first().unwrap(), Path::new("test/fourth.rs"));
|
||||
assert_eq!(matches.search.first().unwrap().as_ref(), rel_path("test/fourth.rs"));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -1626,13 +1679,16 @@ async fn test_search_sorts_history_items(cx: &mut gpui::TestAppContext) {
|
|||
let search_matches = collect_search_matches(finder);
|
||||
assert_eq!(
|
||||
search_matches.history,
|
||||
vec![PathBuf::from("test/1_qw"), PathBuf::from("test/6_qwqwqw"),],
|
||||
vec![
|
||||
rel_path("test/1_qw").into(),
|
||||
rel_path("test/6_qwqwqw").into()
|
||||
],
|
||||
);
|
||||
assert_eq!(
|
||||
search_matches.search,
|
||||
vec![
|
||||
PathBuf::from("test/5_qwqwqw"),
|
||||
PathBuf::from("test/7_qwqwqw"),
|
||||
rel_path("test/5_qwqwqw").into(),
|
||||
rel_path("test/7_qwqwqw").into()
|
||||
],
|
||||
);
|
||||
});
|
||||
|
|
@ -2083,10 +2139,10 @@ async fn test_history_items_vs_very_good_external_match(cx: &mut gpui::TestAppCo
|
|||
assert_eq!(
|
||||
search_entries,
|
||||
vec![
|
||||
PathBuf::from("collab_ui/collab_ui.rs"),
|
||||
PathBuf::from("collab_ui/first.rs"),
|
||||
PathBuf::from("collab_ui/third.rs"),
|
||||
PathBuf::from("collab_ui/second.rs"),
|
||||
rel_path("collab_ui/collab_ui.rs").into(),
|
||||
rel_path("collab_ui/first.rs").into(),
|
||||
rel_path("collab_ui/third.rs").into(),
|
||||
rel_path("collab_ui/second.rs").into(),
|
||||
],
|
||||
"Despite all search results having the same directory name, the most matching one should be on top"
|
||||
);
|
||||
|
|
@ -2135,8 +2191,8 @@ async fn test_nonexistent_history_items_not_shown(cx: &mut gpui::TestAppContext)
|
|||
assert_eq!(
|
||||
collect_search_matches(picker).history,
|
||||
vec![
|
||||
PathBuf::from("test/first.rs"),
|
||||
PathBuf::from("test/third.rs"),
|
||||
rel_path("test/first.rs").into(),
|
||||
rel_path("test/third.rs").into(),
|
||||
],
|
||||
"Should have all opened files in the history, except the ones that do not exist on disk"
|
||||
);
|
||||
|
|
@ -2766,15 +2822,15 @@ fn active_file_picker(
|
|||
|
||||
#[derive(Debug, Default)]
|
||||
struct SearchEntries {
|
||||
history: Vec<PathBuf>,
|
||||
history: Vec<Arc<RelPath>>,
|
||||
history_found_paths: Vec<FoundPath>,
|
||||
search: Vec<PathBuf>,
|
||||
search: Vec<Arc<RelPath>>,
|
||||
search_matches: Vec<PathMatch>,
|
||||
}
|
||||
|
||||
impl SearchEntries {
|
||||
#[track_caller]
|
||||
fn search_paths_only(self) -> Vec<PathBuf> {
|
||||
fn search_paths_only(self) -> Vec<Arc<RelPath>> {
|
||||
assert!(
|
||||
self.history.is_empty(),
|
||||
"Should have no history matches, but got: {:?}",
|
||||
|
|
@ -2802,20 +2858,15 @@ fn collect_search_matches(picker: &Picker<FileFinderDelegate>) -> SearchEntries
|
|||
path: history_path,
|
||||
panel_match: path_match,
|
||||
} => {
|
||||
search_entries.history.push(
|
||||
path_match
|
||||
.as_ref()
|
||||
.map(|path_match| {
|
||||
Path::new(path_match.0.path_prefix.as_ref()).join(&path_match.0.path)
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
history_path
|
||||
.absolute
|
||||
.as_deref()
|
||||
.unwrap_or_else(|| &history_path.project.path)
|
||||
.to_path_buf()
|
||||
}),
|
||||
);
|
||||
if let Some(path_match) = path_match.as_ref() {
|
||||
search_entries
|
||||
.history
|
||||
.push(path_match.0.path_prefix.join(&path_match.0.path));
|
||||
} else {
|
||||
// This occurs when the query is empty and we show history matches
|
||||
// that are outside the project.
|
||||
panic!("currently not exercised in tests");
|
||||
}
|
||||
search_entries
|
||||
.history_found_paths
|
||||
.push(history_path.clone());
|
||||
|
|
@ -2823,7 +2874,7 @@ fn collect_search_matches(picker: &Picker<FileFinderDelegate>) -> SearchEntries
|
|||
Match::Search(path_match) => {
|
||||
search_entries
|
||||
.search
|
||||
.push(Path::new(path_match.0.path_prefix.as_ref()).join(&path_match.0.path));
|
||||
.push(path_match.0.path_prefix.join(&path_match.0.path));
|
||||
search_entries.search_matches.push(path_match.0.clone());
|
||||
}
|
||||
Match::CreateNew(_) => {}
|
||||
|
|
@ -2858,12 +2909,11 @@ fn assert_match_at_position(
|
|||
.get(match_index)
|
||||
.unwrap_or_else(|| panic!("Finder has no match for index {match_index}"));
|
||||
let match_file_name = match &match_item {
|
||||
Match::History { path, .. } => path.absolute.as_deref().unwrap().file_name(),
|
||||
Match::History { path, .. } => path.absolute.file_name().and_then(|s| s.to_str()),
|
||||
Match::Search(path_match) => path_match.0.path.file_name(),
|
||||
Match::CreateNew(project_path) => project_path.path.file_name(),
|
||||
}
|
||||
.unwrap()
|
||||
.to_string_lossy();
|
||||
.unwrap();
|
||||
assert_eq!(match_file_name, expected_file_name);
|
||||
}
|
||||
|
||||
|
|
@ -2901,11 +2951,11 @@ async fn test_filename_precedence(cx: &mut TestAppContext) {
|
|||
assert_eq!(
|
||||
search_matches,
|
||||
vec![
|
||||
PathBuf::from("routes/+layout.svelte"),
|
||||
PathBuf::from("layout/app.css"),
|
||||
PathBuf::from("layout/app.d.ts"),
|
||||
PathBuf::from("layout/app.html"),
|
||||
PathBuf::from("layout/+page.svelte"),
|
||||
rel_path("routes/+layout.svelte").into(),
|
||||
rel_path("layout/app.css").into(),
|
||||
rel_path("layout/app.d.ts").into(),
|
||||
rel_path("layout/app.html").into(),
|
||||
rel_path("layout/+page.svelte").into(),
|
||||
],
|
||||
"File with 'layout' in filename should be prioritized over files in 'layout' directory"
|
||||
);
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use picker::{Picker, PickerDelegate};
|
|||
use project::{DirectoryItem, DirectoryLister};
|
||||
use settings::Settings;
|
||||
use std::{
|
||||
path::{self, MAIN_SEPARATOR_STR, Path, PathBuf},
|
||||
path::{self, Path, PathBuf},
|
||||
sync::{
|
||||
Arc,
|
||||
atomic::{self, AtomicBool},
|
||||
|
|
@ -217,7 +217,7 @@ impl OpenPathPrompt {
|
|||
) {
|
||||
workspace.toggle_modal(window, cx, |window, cx| {
|
||||
let delegate =
|
||||
OpenPathDelegate::new(tx, lister.clone(), creating_path, PathStyle::current());
|
||||
OpenPathDelegate::new(tx, lister.clone(), creating_path, PathStyle::local());
|
||||
let picker = Picker::uniform_list(delegate, window, cx).width(rems(34.));
|
||||
let query = lister.default_query(cx);
|
||||
picker.set_query(query, window, cx);
|
||||
|
|
@ -822,7 +822,7 @@ impl PickerDelegate for OpenPathDelegate {
|
|||
}
|
||||
|
||||
fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
|
||||
Arc::from(format!("[directory{MAIN_SEPARATOR_STR}]filename.ext"))
|
||||
Arc::from(format!("[directory{}]filename.ext", self.path_style.separator()).as_str())
|
||||
}
|
||||
|
||||
fn separators_after_indices(&self) -> Vec<usize> {
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ async fn test_open_path_prompt(cx: &mut TestAppContext) {
|
|||
|
||||
let project = Project::test(app_state.fs.clone(), [path!("/root").as_ref()], cx).await;
|
||||
|
||||
let (picker, cx) = build_open_path_prompt(project, false, PathStyle::current(), cx);
|
||||
let (picker, cx) = build_open_path_prompt(project, false, PathStyle::local(), cx);
|
||||
|
||||
insert_query(path!("sadjaoislkdjasldj"), &picker, cx).await;
|
||||
assert_eq!(collect_match_candidates(&picker, cx), Vec::<String>::new());
|
||||
|
|
@ -119,7 +119,7 @@ async fn test_open_path_prompt_completion(cx: &mut TestAppContext) {
|
|||
|
||||
let project = Project::test(app_state.fs.clone(), [path!("/root").as_ref()], cx).await;
|
||||
|
||||
let (picker, cx) = build_open_path_prompt(project, false, PathStyle::current(), cx);
|
||||
let (picker, cx) = build_open_path_prompt(project, false, PathStyle::local(), cx);
|
||||
|
||||
// Confirm completion for the query "/root", since it's a directory, it should add a trailing slash.
|
||||
let query = path!("/root");
|
||||
|
|
@ -227,7 +227,7 @@ async fn test_open_path_prompt_on_windows(cx: &mut TestAppContext) {
|
|||
|
||||
let project = Project::test(app_state.fs.clone(), [path!("/root").as_ref()], cx).await;
|
||||
|
||||
let (picker, cx) = build_open_path_prompt(project, false, PathStyle::current(), cx);
|
||||
let (picker, cx) = build_open_path_prompt(project, false, PathStyle::local(), cx);
|
||||
|
||||
// Support both forward and backward slashes.
|
||||
let query = "C:/root/";
|
||||
|
|
@ -372,7 +372,7 @@ async fn test_new_path_prompt(cx: &mut TestAppContext) {
|
|||
|
||||
let project = Project::test(app_state.fs.clone(), [path!("/root").as_ref()], cx).await;
|
||||
|
||||
let (picker, cx) = build_open_path_prompt(project, true, PathStyle::current(), cx);
|
||||
let (picker, cx) = build_open_path_prompt(project, true, PathStyle::local(), cx);
|
||||
|
||||
insert_query(path!("/root"), &picker, cx).await;
|
||||
assert_eq!(collect_match_candidates(&picker, cx), vec!["root"]);
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ use parking_lot::Mutex;
|
|||
use rope::Rope;
|
||||
use smol::future::FutureExt as _;
|
||||
use std::{path::PathBuf, sync::Arc};
|
||||
use util::{paths::PathStyle, rel_path::RelPath};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct FakeGitRepository {
|
||||
|
|
@ -82,7 +83,7 @@ impl GitRepository for FakeGitRepository {
|
|||
self.with_state_async(false, move |state| {
|
||||
state
|
||||
.index_contents
|
||||
.get(path.as_ref())
|
||||
.get(&path)
|
||||
.context("not present in index")
|
||||
.cloned()
|
||||
})
|
||||
|
|
@ -97,7 +98,7 @@ impl GitRepository for FakeGitRepository {
|
|||
self.with_state_async(false, move |state| {
|
||||
state
|
||||
.head_contents
|
||||
.get(path.as_ref())
|
||||
.get(&path)
|
||||
.context("not present in HEAD")
|
||||
.cloned()
|
||||
})
|
||||
|
|
@ -225,6 +226,7 @@ impl GitRepository for FakeGitRepository {
|
|||
.read_file_sync(path)
|
||||
.ok()
|
||||
.map(|content| String::from_utf8(content).unwrap())?;
|
||||
let repo_path = RelPath::from_std_path(repo_path, PathStyle::local()).ok()?;
|
||||
Some((repo_path.into(), (content, is_ignored)))
|
||||
})
|
||||
.collect();
|
||||
|
|
@ -386,7 +388,11 @@ impl GitRepository for FakeGitRepository {
|
|||
let contents = paths
|
||||
.into_iter()
|
||||
.map(|path| {
|
||||
let abs_path = self.dot_git_path.parent().unwrap().join(&path);
|
||||
let abs_path = self
|
||||
.dot_git_path
|
||||
.parent()
|
||||
.unwrap()
|
||||
.join(&path.as_std_path());
|
||||
Box::pin(async move { (path.clone(), self.fs.load(&abs_path).await.ok()) })
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ use collections::{BTreeMap, btree_map};
|
|||
use fake_git_repo::FakeGitRepositoryState;
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
use git::{
|
||||
repository::RepoPath,
|
||||
repository::{RepoPath, repo_path},
|
||||
status::{FileStatus, StatusCode, TrackedStatus, UnmergedStatus},
|
||||
};
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
|
|
@ -1608,13 +1608,13 @@ impl FakeFs {
|
|||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn set_index_for_repo(&self, dot_git: &Path, index_state: &[(RepoPath, String)]) {
|
||||
pub fn set_index_for_repo(&self, dot_git: &Path, index_state: &[(&str, String)]) {
|
||||
self.with_git_state(dot_git, true, |state| {
|
||||
state.index_contents.clear();
|
||||
state.index_contents.extend(
|
||||
index_state
|
||||
.iter()
|
||||
.map(|(path, content)| (path.clone(), content.clone())),
|
||||
.map(|(path, content)| (repo_path(path), content.clone())),
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
|
|
@ -1623,7 +1623,7 @@ impl FakeFs {
|
|||
pub fn set_head_for_repo(
|
||||
&self,
|
||||
dot_git: &Path,
|
||||
head_state: &[(RepoPath, String)],
|
||||
head_state: &[(&str, String)],
|
||||
sha: impl Into<String>,
|
||||
) {
|
||||
self.with_git_state(dot_git, true, |state| {
|
||||
|
|
@ -1631,50 +1631,22 @@ impl FakeFs {
|
|||
state.head_contents.extend(
|
||||
head_state
|
||||
.iter()
|
||||
.map(|(path, content)| (path.clone(), content.clone())),
|
||||
.map(|(path, content)| (repo_path(path), content.clone())),
|
||||
);
|
||||
state.refs.insert("HEAD".into(), sha.into());
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn set_git_content_for_repo(
|
||||
&self,
|
||||
dot_git: &Path,
|
||||
head_state: &[(RepoPath, String, Option<String>)],
|
||||
) {
|
||||
pub fn set_head_and_index_for_repo(&self, dot_git: &Path, contents_by_path: &[(&str, String)]) {
|
||||
self.with_git_state(dot_git, true, |state| {
|
||||
state.head_contents.clear();
|
||||
state.head_contents.extend(
|
||||
head_state
|
||||
contents_by_path
|
||||
.iter()
|
||||
.map(|(path, head_content, _)| (path.clone(), head_content.clone())),
|
||||
.map(|(path, contents)| (repo_path(path), contents.clone())),
|
||||
);
|
||||
state.index_contents.clear();
|
||||
state.index_contents.extend(head_state.iter().map(
|
||||
|(path, head_content, index_content)| {
|
||||
(
|
||||
path.clone(),
|
||||
index_content.as_ref().unwrap_or(head_content).clone(),
|
||||
)
|
||||
},
|
||||
));
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn set_head_and_index_for_repo(
|
||||
&self,
|
||||
dot_git: &Path,
|
||||
contents_by_path: &[(RepoPath, String)],
|
||||
) {
|
||||
self.with_git_state(dot_git, true, |state| {
|
||||
state.head_contents.clear();
|
||||
state.index_contents.clear();
|
||||
state.head_contents.extend(contents_by_path.iter().cloned());
|
||||
state
|
||||
.index_contents
|
||||
.extend(contents_by_path.iter().cloned());
|
||||
state.index_contents = state.head_contents.clone();
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
|
@ -1689,7 +1661,7 @@ impl FakeFs {
|
|||
|
||||
/// Put the given git repository into a state with the given status,
|
||||
/// by mutating the head, index, and unmerged state.
|
||||
pub fn set_status_for_repo(&self, dot_git: &Path, statuses: &[(&Path, FileStatus)]) {
|
||||
pub fn set_status_for_repo(&self, dot_git: &Path, statuses: &[(&str, FileStatus)]) {
|
||||
let workdir_path = dot_git.parent().unwrap();
|
||||
let workdir_contents = self.files_with_contents(workdir_path);
|
||||
self.with_git_state(dot_git, true, |state| {
|
||||
|
|
@ -1697,10 +1669,12 @@ impl FakeFs {
|
|||
state.head_contents.clear();
|
||||
state.unmerged_paths.clear();
|
||||
for (path, content) in workdir_contents {
|
||||
let repo_path: RepoPath = path.strip_prefix(&workdir_path).unwrap().into();
|
||||
use util::{paths::PathStyle, rel_path::RelPath};
|
||||
|
||||
let repo_path: RepoPath = RelPath::from_std_path(path.strip_prefix(&workdir_path).unwrap(), PathStyle::local()).unwrap().into();
|
||||
let status = statuses
|
||||
.iter()
|
||||
.find_map(|(p, status)| (**p == *repo_path.0).then_some(status));
|
||||
.find_map(|(p, status)| (*p == repo_path.as_str()).then_some(status));
|
||||
let mut content = String::from_utf8_lossy(&content).to_string();
|
||||
|
||||
let mut index_content = None;
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue