windows: Fix tests on Windows (#22616)

Release Notes:

- N/A

---------

Co-authored-by: Mikayla <mikayla.c.maki@gmail.com>
This commit is contained in:
张小白 2025-02-05 22:30:09 +08:00 committed by GitHub
parent c252b5db16
commit 74c4dbd237
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
56 changed files with 1540 additions and 856 deletions

View file

@ -0,0 +1,26 @@
name: "Run tests on Windows"
description: "Runs the tests on Windows"
inputs:
working-directory:
description: "The working directory"
required: true
default: "."
runs:
using: "composite"
steps:
- name: Install Rust
shell: pwsh
working-directory: ${{ inputs.working-directory }}
run: cargo install cargo-nextest --locked
- name: Install Node
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4
with:
node-version: "18"
- name: Run tests
shell: pwsh
working-directory: ${{ inputs.working-directory }}
run: cargo nextest run --workspace --no-fail-fast

View file

@ -228,7 +228,6 @@ jobs:
if: always() if: always()
run: rm -rf ./../.cargo run: rm -rf ./../.cargo
# todo(windows): Actually run the tests
windows_tests: windows_tests:
timeout-minutes: 60 timeout-minutes: 60
name: (Windows) Run Clippy and tests name: (Windows) Run Clippy and tests
@ -269,10 +268,19 @@ jobs:
# Windows can't run shell scripts, so we need to use `cargo xtask`. # Windows can't run shell scripts, so we need to use `cargo xtask`.
run: cargo xtask clippy run: cargo xtask clippy
- name: Run tests
uses: ./.github/actions/run_tests_windows
with:
working-directory: ${{ env.ZED_WORKSPACE }}
- name: Build Zed - name: Build Zed
working-directory: ${{ env.ZED_WORKSPACE }} working-directory: ${{ env.ZED_WORKSPACE }}
run: cargo build run: cargo build
- name: Check dev drive space
working-directory: ${{ env.ZED_WORKSPACE }}
run: ./script/exit-ci-if-dev-drive-is-full.ps1 55
# Since the Windows runners are stateful, so we need to remove the config file to prevent potential bug. # Since the Windows runners are stateful, so we need to remove the config file to prevent potential bug.
- name: Clean CI config file - name: Clean CI config file
if: always() if: always()

9
Cargo.lock generated
View file

@ -14404,6 +14404,15 @@ dependencies = [
"tempfile", "tempfile",
"tendril", "tendril",
"unicase", "unicase",
"util_macros",
]
[[package]]
name = "util_macros"
version = "0.1.0"
dependencies = [
"quote",
"syn 1.0.109",
] ]
[[package]] [[package]]

View file

@ -144,6 +144,7 @@ members = [
"crates/ui_input", "crates/ui_input",
"crates/ui_macros", "crates/ui_macros",
"crates/util", "crates/util",
"crates/util_macros",
"crates/vcs_menu", "crates/vcs_menu",
"crates/vim", "crates/vim",
"crates/vim_mode_setting", "crates/vim_mode_setting",
@ -339,6 +340,7 @@ ui = { path = "crates/ui" }
ui_input = { path = "crates/ui_input" } ui_input = { path = "crates/ui_input" }
ui_macros = { path = "crates/ui_macros" } ui_macros = { path = "crates/ui_macros" }
util = { path = "crates/util" } util = { path = "crates/util" }
util_macros = { path = "crates/util_macros" }
vcs_menu = { path = "crates/vcs_menu" } vcs_menu = { path = "crates/vcs_menu" }
vim = { path = "crates/vim" } vim = { path = "crates/vim" }
vim_mode_setting = { path = "crates/vim_mode_setting" } vim_mode_setting = { path = "crates/vim_mode_setting" }
@ -359,7 +361,7 @@ alacritty_terminal = { git = "https://github.com/alacritty/alacritty.git", rev =
any_vec = "0.14" any_vec = "0.14"
anyhow = "1.0.86" anyhow = "1.0.86"
arrayvec = { version = "0.7.4", features = ["serde"] } arrayvec = { version = "0.7.4", features = ["serde"] }
ashpd = { version = "0.10", default-features = false, features = ["async-std"]} ashpd = { version = "0.10", default-features = false, features = ["async-std"] }
async-compat = "0.2.1" async-compat = "0.2.1"
async-compression = { version = "0.4", features = ["gzip", "futures-io"] } async-compression = { version = "0.4", features = ["gzip", "futures-io"] }
async-dispatcher = "0.1" async-dispatcher = "0.1"
@ -421,7 +423,11 @@ jupyter-websocket-client = { version = "0.9.0" }
libc = "0.2" libc = "0.2"
libsqlite3-sys = { version = "0.30.1", features = ["bundled"] } libsqlite3-sys = { version = "0.30.1", features = ["bundled"] }
linkify = "0.10.0" linkify = "0.10.0"
livekit = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev="811ceae29fabee455f110c56cd66b3f49a7e5003", features = ["dispatcher", "services-dispatcher", "rustls-tls-native-roots"], default-features = false } livekit = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "811ceae29fabee455f110c56cd66b3f49a7e5003", features = [
"dispatcher",
"services-dispatcher",
"rustls-tls-native-roots",
], default-features = false }
log = { version = "0.4.16", features = ["kv_unstable_serde", "serde"] } log = { version = "0.4.16", features = ["kv_unstable_serde", "serde"] }
markup5ever_rcdom = "0.3.0" markup5ever_rcdom = "0.3.0"
nanoid = "0.4" nanoid = "0.4"
@ -441,11 +447,13 @@ pet-poetry = { git = "https://github.com/microsoft/python-environment-tools.git"
pet-reporter = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0" } pet-reporter = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0" }
postage = { version = "0.5", features = ["futures-traits"] } postage = { version = "0.5", features = ["futures-traits"] }
pretty_assertions = { version = "1.3.0", features = ["unstable"] } pretty_assertions = { version = "1.3.0", features = ["unstable"] }
proc-macro2 = "1.0.93"
profiling = "1" profiling = "1"
prost = "0.9" prost = "0.9"
prost-build = "0.9" prost-build = "0.9"
prost-types = "0.9" prost-types = "0.9"
pulldown-cmark = { version = "0.12.0", default-features = false } pulldown-cmark = { version = "0.12.0", default-features = false }
quote = "1.0.9"
rand = "0.8.5" rand = "0.8.5"
rayon = "1.8" rayon = "1.8"
regex = "1.5" regex = "1.5"
@ -489,6 +497,7 @@ sqlformat = "0.2"
strsim = "0.11" strsim = "0.11"
strum = { version = "0.26.0", features = ["derive"] } strum = { version = "0.26.0", features = ["derive"] }
subtle = "2.5.0" subtle = "2.5.0"
syn = { version = "1.0.72", features = ["full", "extra-traits"] }
sys-locale = "0.3.1" sys-locale = "0.3.1"
sysinfo = "0.31.0" sysinfo = "0.31.0"
take-until = "0.2.0" take-until = "0.2.0"

View file

@ -323,7 +323,14 @@ fn collect_files(
)))?; )))?;
directory_stack.push(entry.path.clone()); directory_stack.push(entry.path.clone());
} else { } else {
let entry_name = format!("{}/{}", prefix_paths, &filename); // 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
);
events_tx.unbounded_send(Ok(SlashCommandEvent::StartSection { events_tx.unbounded_send(Ok(SlashCommandEvent::StartSection {
icon: IconName::Folder, icon: IconName::Folder,
label: entry_name.clone().into(), label: entry_name.clone().into(),
@ -455,6 +462,7 @@ mod custom_path_matcher {
use std::{fmt::Debug as _, path::Path}; use std::{fmt::Debug as _, path::Path};
use globset::{Glob, GlobSet, GlobSetBuilder}; use globset::{Glob, GlobSet, GlobSetBuilder};
use util::paths::SanitizedPath;
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct PathMatcher { pub struct PathMatcher {
@ -481,7 +489,7 @@ mod custom_path_matcher {
pub fn new(globs: &[String]) -> Result<Self, globset::Error> { pub fn new(globs: &[String]) -> Result<Self, globset::Error> {
let globs = globs let globs = globs
.into_iter() .into_iter()
.map(|glob| Glob::new(&glob)) .map(|glob| Glob::new(&SanitizedPath::from(glob).to_glob_string()))
.collect::<Result<Vec<_>, _>>()?; .collect::<Result<Vec<_>, _>>()?;
let sources = globs.iter().map(|glob| glob.glob().to_owned()).collect(); let sources = globs.iter().map(|glob| glob.glob().to_owned()).collect();
let sources_with_trailing_slash = globs let sources_with_trailing_slash = globs
@ -507,7 +515,9 @@ mod custom_path_matcher {
.zip(self.sources_with_trailing_slash.iter()) .zip(self.sources_with_trailing_slash.iter())
.any(|(source, with_slash)| { .any(|(source, with_slash)| {
let as_bytes = other_path.as_os_str().as_encoded_bytes(); let as_bytes = other_path.as_os_str().as_encoded_bytes();
let with_slash = if source.ends_with("/") { // 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) {
source.as_bytes() source.as_bytes()
} else { } else {
with_slash.as_bytes() with_slash.as_bytes()
@ -569,6 +579,7 @@ mod test {
use serde_json::json; use serde_json::json;
use settings::SettingsStore; use settings::SettingsStore;
use smol::stream::StreamExt; use smol::stream::StreamExt;
use util::{path, separator};
use super::collect_files; use super::collect_files;
@ -592,7 +603,7 @@ mod test {
let fs = FakeFs::new(cx.executor()); let fs = FakeFs::new(cx.executor());
fs.insert_tree( fs.insert_tree(
"/root", path!("/root"),
json!({ json!({
"dir": { "dir": {
"subdir": { "subdir": {
@ -607,7 +618,7 @@ mod test {
) )
.await; .await;
let project = Project::test(fs, ["/root".as_ref()], cx).await; let project = Project::test(fs, [path!("/root").as_ref()], cx).await;
let result_1 = let result_1 =
cx.update(|cx| collect_files(project.clone(), &["root/dir".to_string()], cx)); cx.update(|cx| collect_files(project.clone(), &["root/dir".to_string()], cx));
@ -615,7 +626,7 @@ mod test {
.await .await
.unwrap(); .unwrap();
assert!(result_1.text.starts_with("root/dir")); assert!(result_1.text.starts_with(separator!("root/dir")));
// 4 files + 2 directories // 4 files + 2 directories
assert_eq!(result_1.sections.len(), 6); assert_eq!(result_1.sections.len(), 6);
@ -631,7 +642,7 @@ mod test {
cx.update(|cx| collect_files(project.clone(), &["root/dir*".to_string()], cx).boxed()); cx.update(|cx| collect_files(project.clone(), &["root/dir*".to_string()], cx).boxed());
let result = SlashCommandOutput::from_event_stream(result).await.unwrap(); let result = SlashCommandOutput::from_event_stream(result).await.unwrap();
assert!(result.text.starts_with("root/dir")); assert!(result.text.starts_with(separator!("root/dir")));
// 5 files + 2 directories // 5 files + 2 directories
assert_eq!(result.sections.len(), 7); assert_eq!(result.sections.len(), 7);
@ -645,7 +656,7 @@ mod test {
let fs = FakeFs::new(cx.executor()); let fs = FakeFs::new(cx.executor());
fs.insert_tree( fs.insert_tree(
"/zed", path!("/zed"),
json!({ json!({
"assets": { "assets": {
"dir1": { "dir1": {
@ -670,7 +681,7 @@ mod test {
) )
.await; .await;
let project = Project::test(fs, ["/zed".as_ref()], cx).await; let project = Project::test(fs, [path!("/zed").as_ref()], cx).await;
let result = let result =
cx.update(|cx| collect_files(project.clone(), &["zed/assets/themes".to_string()], cx)); cx.update(|cx| collect_files(project.clone(), &["zed/assets/themes".to_string()], cx));
@ -679,27 +690,36 @@ mod test {
.unwrap(); .unwrap();
// Sanity check // Sanity check
assert!(result.text.starts_with("zed/assets/themes\n")); assert!(result.text.starts_with(separator!("zed/assets/themes\n")));
assert_eq!(result.sections.len(), 7); assert_eq!(result.sections.len(), 7);
// Ensure that full file paths are included in the real output // Ensure that full file paths are included in the real output
assert!(result.text.contains("zed/assets/themes/andromeda/LICENSE")); assert!(result
assert!(result.text.contains("zed/assets/themes/ayu/LICENSE")); .text
assert!(result.text.contains("zed/assets/themes/summercamp/LICENSE")); .contains(separator!("zed/assets/themes/andromeda/LICENSE")));
assert!(result
.text
.contains(separator!("zed/assets/themes/ayu/LICENSE")));
assert!(result
.text
.contains(separator!("zed/assets/themes/summercamp/LICENSE")));
assert_eq!(result.sections[5].label, "summercamp"); assert_eq!(result.sections[5].label, "summercamp");
// Ensure that things are in descending order, with properly relativized paths // Ensure that things are in descending order, with properly relativized paths
assert_eq!( assert_eq!(
result.sections[0].label, result.sections[0].label,
"zed/assets/themes/andromeda/LICENSE" separator!("zed/assets/themes/andromeda/LICENSE")
); );
assert_eq!(result.sections[1].label, "andromeda"); assert_eq!(result.sections[1].label, "andromeda");
assert_eq!(result.sections[2].label, "zed/assets/themes/ayu/LICENSE"); assert_eq!(
result.sections[2].label,
separator!("zed/assets/themes/ayu/LICENSE")
);
assert_eq!(result.sections[3].label, "ayu"); assert_eq!(result.sections[3].label, "ayu");
assert_eq!( assert_eq!(
result.sections[4].label, result.sections[4].label,
"zed/assets/themes/summercamp/LICENSE" separator!("zed/assets/themes/summercamp/LICENSE")
); );
// Ensure that the project lasts until after the last await // Ensure that the project lasts until after the last await
@ -712,7 +732,7 @@ mod test {
let fs = FakeFs::new(cx.executor()); let fs = FakeFs::new(cx.executor());
fs.insert_tree( fs.insert_tree(
"/zed", path!("/zed"),
json!({ json!({
"assets": { "assets": {
"themes": { "themes": {
@ -732,7 +752,7 @@ mod test {
) )
.await; .await;
let project = Project::test(fs, ["/zed".as_ref()], cx).await; let project = Project::test(fs, [path!("/zed").as_ref()], cx).await;
let result = let result =
cx.update(|cx| collect_files(project.clone(), &["zed/assets/themes".to_string()], cx)); cx.update(|cx| collect_files(project.clone(), &["zed/assets/themes".to_string()], cx));
@ -740,26 +760,29 @@ mod test {
.await .await
.unwrap(); .unwrap();
assert!(result.text.starts_with("zed/assets/themes\n")); assert!(result.text.starts_with(separator!("zed/assets/themes\n")));
assert_eq!(result.sections[0].label, "zed/assets/themes/LICENSE"); assert_eq!(
result.sections[0].label,
separator!("zed/assets/themes/LICENSE")
);
assert_eq!( assert_eq!(
result.sections[1].label, result.sections[1].label,
"zed/assets/themes/summercamp/LICENSE" separator!("zed/assets/themes/summercamp/LICENSE")
); );
assert_eq!( assert_eq!(
result.sections[2].label, result.sections[2].label,
"zed/assets/themes/summercamp/subdir/LICENSE" separator!("zed/assets/themes/summercamp/subdir/LICENSE")
); );
assert_eq!( assert_eq!(
result.sections[3].label, result.sections[3].label,
"zed/assets/themes/summercamp/subdir/subsubdir/LICENSE" separator!("zed/assets/themes/summercamp/subdir/subsubdir/LICENSE")
); );
assert_eq!(result.sections[4].label, "subsubdir"); assert_eq!(result.sections[4].label, "subsubdir");
assert_eq!(result.sections[5].label, "subdir"); assert_eq!(result.sections[5].label, "subdir");
assert_eq!(result.sections[6].label, "summercamp"); assert_eq!(result.sections[6].label, "summercamp");
assert_eq!(result.sections[7].label, "zed/assets/themes"); assert_eq!(result.sections[7].label, separator!("zed/assets/themes"));
assert_eq!(result.text, "zed/assets/themes\n```zed/assets/themes/LICENSE\n1\n```\n\nsummercamp\n```zed/assets/themes/summercamp/LICENSE\n1\n```\n\nsubdir\n```zed/assets/themes/summercamp/subdir/LICENSE\n1\n```\n\nsubsubdir\n```zed/assets/themes/summercamp/subdir/subsubdir/LICENSE\n3\n```\n\n"); assert_eq!(result.text, separator!("zed/assets/themes\n```zed/assets/themes/LICENSE\n1\n```\n\nsummercamp\n```zed/assets/themes/summercamp/LICENSE\n1\n```\n\nsubdir\n```zed/assets/themes/summercamp/subdir/LICENSE\n1\n```\n\nsubsubdir\n```zed/assets/themes/summercamp/subdir/subsubdir/LICENSE\n3\n```\n\n"));
// Ensure that the project lasts until after the last await // Ensure that the project lasts until after the last await
drop(project); drop(project);

View file

@ -1061,6 +1061,7 @@ async fn get_copilot_lsp(http: Arc<dyn HttpClient>) -> anyhow::Result<PathBuf> {
mod tests { mod tests {
use super::*; use super::*;
use gpui::TestAppContext; use gpui::TestAppContext;
use util::path;
#[gpui::test(iterations = 10)] #[gpui::test(iterations = 10)]
async fn test_buffer_management(cx: &mut TestAppContext) { async fn test_buffer_management(cx: &mut TestAppContext) {
@ -1123,7 +1124,7 @@ mod tests {
buffer_1.update(cx, |buffer, cx| { buffer_1.update(cx, |buffer, cx| {
buffer.file_updated( buffer.file_updated(
Arc::new(File { Arc::new(File {
abs_path: "/root/child/buffer-1".into(), abs_path: path!("/root/child/buffer-1").into(),
path: Path::new("child/buffer-1").into(), path: Path::new("child/buffer-1").into(),
}), }),
cx, cx,
@ -1136,7 +1137,7 @@ mod tests {
text_document: lsp::TextDocumentIdentifier::new(buffer_1_uri), text_document: lsp::TextDocumentIdentifier::new(buffer_1_uri),
} }
); );
let buffer_1_uri = lsp::Url::from_file_path("/root/child/buffer-1").unwrap(); let buffer_1_uri = lsp::Url::from_file_path(path!("/root/child/buffer-1")).unwrap();
assert_eq!( assert_eq!(
lsp.receive_notification::<lsp::notification::DidOpenTextDocument>() lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
.await, .await,

View file

@ -290,7 +290,10 @@ mod tests {
use serde_json::json; use serde_json::json;
use settings::SettingsStore; use settings::SettingsStore;
use std::future::Future; use std::future::Future;
use util::test::{marked_text_ranges_by, TextRangeMarker}; use util::{
path,
test::{marked_text_ranges_by, TextRangeMarker},
};
#[gpui::test(iterations = 10)] #[gpui::test(iterations = 10)]
async fn test_copilot(executor: BackgroundExecutor, cx: &mut TestAppContext) { async fn test_copilot(executor: BackgroundExecutor, cx: &mut TestAppContext) {
@ -949,24 +952,24 @@ mod tests {
let fs = FakeFs::new(cx.executor()); let fs = FakeFs::new(cx.executor());
fs.insert_tree( fs.insert_tree(
"/test", path!("/test"),
json!({ json!({
".env": "SECRET=something\n", ".env": "SECRET=something\n",
"README.md": "hello\nworld\nhow\nare\nyou\ntoday" "README.md": "hello\nworld\nhow\nare\nyou\ntoday"
}), }),
) )
.await; .await;
let project = Project::test(fs, ["/test".as_ref()], cx).await; let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
let private_buffer = project let private_buffer = project
.update(cx, |project, cx| { .update(cx, |project, cx| {
project.open_local_buffer("/test/.env", cx) project.open_local_buffer(path!("/test/.env"), cx)
}) })
.await .await
.unwrap(); .unwrap();
let public_buffer = project let public_buffer = project
.update(cx, |project, cx| { .update(cx, |project, cx| {
project.open_local_buffer("/test/README.md", cx) project.open_local_buffer(path!("/test/README.md"), cx)
}) })
.await .await
.unwrap(); .unwrap();

View file

@ -1433,7 +1433,10 @@ impl ToDisplayPoint for Anchor {
#[cfg(test)] #[cfg(test)]
pub mod tests { pub mod tests {
use super::*; use super::*;
use crate::{movement, test::marked_display_snapshot}; use crate::{
movement,
test::{marked_display_snapshot, test_font},
};
use block_map::BlockPlacement; use block_map::BlockPlacement;
use gpui::{ use gpui::{
div, font, observe, px, App, AppContext as _, BorrowAppContext, Element, Hsla, Rgba, div, font, observe, px, App, AppContext as _, BorrowAppContext, Element, Hsla, Rgba,
@ -1492,10 +1495,11 @@ pub mod tests {
} }
}); });
let font = test_font();
let map = cx.new(|cx| { let map = cx.new(|cx| {
DisplayMap::new( DisplayMap::new(
buffer.clone(), buffer.clone(),
font("Helvetica"), font,
font_size, font_size,
wrap_width, wrap_width,
true, true,

View file

@ -1992,8 +1992,9 @@ fn offset_for_row(s: &str, target: u32) -> (u32, usize) {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::display_map::{ use crate::{
fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap, wrap_map::WrapMap, display_map::{fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap, wrap_map::WrapMap},
test::test_font,
}; };
use gpui::{div, font, px, App, AppContext as _, Element}; use gpui::{div, font, px, App, AppContext as _, Element};
use itertools::Itertools; use itertools::Itertools;
@ -2227,7 +2228,7 @@ mod tests {
multi_buffer multi_buffer
}); });
let font = font("Helvetica"); let font = test_font();
let font_size = px(14.); let font_size = px(14.);
let font_id = cx.text_system().resolve_font(&font); let font_id = cx.text_system().resolve_font(&font);
let mut wrap_width = px(0.); let mut wrap_width = px(0.);
@ -3069,8 +3070,9 @@ mod tests {
let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot); let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap()); let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
let (wrap_map, wraps_snapshot) = cx let font = test_font();
.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), font_size, wrap_width, cx)); let (wrap_map, wraps_snapshot) =
cx.update(|cx| WrapMap::new(tab_snapshot, font, font_size, wrap_width, cx));
let mut block_map = BlockMap::new( let mut block_map = BlockMap::new(
wraps_snapshot, wraps_snapshot,
true, true,

View file

@ -1169,9 +1169,10 @@ mod tests {
use super::*; use super::*;
use crate::{ use crate::{
display_map::{fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap}, display_map::{fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap},
test::test_font,
MultiBuffer, MultiBuffer,
}; };
use gpui::{font, px, test::observe}; use gpui::{px, test::observe};
use rand::prelude::*; use rand::prelude::*;
use settings::SettingsStore; use settings::SettingsStore;
use smol::stream::StreamExt; use smol::stream::StreamExt;
@ -1196,7 +1197,8 @@ mod tests {
Some(px(rng.gen_range(0.0..=1000.0))) Some(px(rng.gen_range(0.0..=1000.0)))
}; };
let tab_size = NonZeroU32::new(rng.gen_range(1..=4)).unwrap(); let tab_size = NonZeroU32::new(rng.gen_range(1..=4)).unwrap();
let font = font("Helvetica");
let font = test_font();
let _font_id = text_system.font_id(&font); let _font_id = text_system.font_id(&font);
let font_size = px(14.0); let font_size = px(14.0);

View file

@ -40,8 +40,9 @@ use std::{
use test::{build_editor_with_project, editor_lsp_test_context::rust_lang}; use test::{build_editor_with_project, editor_lsp_test_context::rust_lang};
use unindent::Unindent; use unindent::Unindent;
use util::{ use util::{
assert_set_eq, assert_set_eq, path,
test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker}, test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
uri,
}; };
use workspace::{ use workspace::{
item::{FollowEvent, FollowableItem, Item, ItemHandle}, item::{FollowEvent, FollowableItem, Item, ItemHandle},
@ -7074,9 +7075,9 @@ async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {}); init_test(cx, |_| {});
let fs = FakeFs::new(cx.executor()); let fs = FakeFs::new(cx.executor());
fs.insert_file("/file.rs", Default::default()).await; fs.insert_file(path!("/file.rs"), Default::default()).await;
let project = Project::test(fs, ["/file.rs".as_ref()], cx).await; let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
let language_registry = project.read_with(cx, |project, _| project.languages().clone()); let language_registry = project.read_with(cx, |project, _| project.languages().clone());
language_registry.add(rust_lang()); language_registry.add(rust_lang());
@ -7092,7 +7093,9 @@ async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
); );
let buffer = project let buffer = project
.update(cx, |project, cx| project.open_local_buffer("/file.rs", cx)) .update(cx, |project, cx| {
project.open_local_buffer(path!("/file.rs"), cx)
})
.await .await
.unwrap(); .unwrap();
@ -7117,7 +7120,7 @@ async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move { .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
assert_eq!( assert_eq!(
params.text_document.uri, params.text_document.uri,
lsp::Url::from_file_path("/file.rs").unwrap() lsp::Url::from_file_path(path!("/file.rs")).unwrap()
); );
assert_eq!(params.options.tab_size, 4); assert_eq!(params.options.tab_size, 4);
Ok(Some(vec![lsp::TextEdit::new( Ok(Some(vec![lsp::TextEdit::new(
@ -7145,7 +7148,7 @@ async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move { fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
assert_eq!( assert_eq!(
params.text_document.uri, params.text_document.uri,
lsp::Url::from_file_path("/file.rs").unwrap() lsp::Url::from_file_path(path!("/file.rs")).unwrap()
); );
futures::future::pending::<()>().await; futures::future::pending::<()>().await;
unreachable!() unreachable!()
@ -7202,7 +7205,7 @@ async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move { .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
assert_eq!( assert_eq!(
params.text_document.uri, params.text_document.uri,
lsp::Url::from_file_path("/file.rs").unwrap() lsp::Url::from_file_path(path!("/file.rs")).unwrap()
); );
assert_eq!(params.options.tab_size, 8); assert_eq!(params.options.tab_size, 8);
Ok(Some(vec![])) Ok(Some(vec![]))
@ -7237,7 +7240,7 @@ async fn test_multibuffer_format_during_save(cx: &mut gpui::TestAppContext) {
let fs = FakeFs::new(cx.executor()); let fs = FakeFs::new(cx.executor());
fs.insert_tree( fs.insert_tree(
"/a", path!("/a"),
json!({ json!({
"main.rs": sample_text_1, "main.rs": sample_text_1,
"other.rs": sample_text_2, "other.rs": sample_text_2,
@ -7246,7 +7249,7 @@ async fn test_multibuffer_format_during_save(cx: &mut gpui::TestAppContext) {
) )
.await; .await;
let project = Project::test(fs, ["/a".as_ref()], cx).await; let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx)); let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx); let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
@ -7421,20 +7424,20 @@ async fn test_multibuffer_format_during_save(cx: &mut gpui::TestAppContext) {
assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx))); assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
assert_eq!( assert_eq!(
multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)), multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
"a|o[file:///a/main.rs formatted]bbbb\ncccc\n\nffff\ngggg\n\njjjj\n\nlll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|\nr\n\nuuuu\n\nvvvv\nwwww\nxxxx\n\n{{{{\n||||\n\n\u{7f}\u{7f}\u{7f}\u{7f}", uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\n\nffff\ngggg\n\njjjj\n\nlll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|\nr\n\nuuuu\n\nvvvv\nwwww\nxxxx\n\n{{{{\n||||\n\n\u{7f}\u{7f}\u{7f}\u{7f}"),
); );
buffer_1.update(cx, |buffer, _| { buffer_1.update(cx, |buffer, _| {
assert!(!buffer.is_dirty()); assert!(!buffer.is_dirty());
assert_eq!( assert_eq!(
buffer.text(), buffer.text(),
"a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n", uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
) )
}); });
buffer_2.update(cx, |buffer, _| { buffer_2.update(cx, |buffer, _| {
assert!(!buffer.is_dirty()); assert!(!buffer.is_dirty());
assert_eq!( assert_eq!(
buffer.text(), buffer.text(),
"lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n", uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
) )
}); });
buffer_3.update(cx, |buffer, _| { buffer_3.update(cx, |buffer, _| {
@ -7448,9 +7451,9 @@ async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {}); init_test(cx, |_| {});
let fs = FakeFs::new(cx.executor()); let fs = FakeFs::new(cx.executor());
fs.insert_file("/file.rs", Default::default()).await; fs.insert_file(path!("/file.rs"), Default::default()).await;
let project = Project::test(fs, ["/file.rs".as_ref()], cx).await; let project = Project::test(fs, [path!("/").as_ref()], cx).await;
let language_registry = project.read_with(cx, |project, _| project.languages().clone()); let language_registry = project.read_with(cx, |project, _| project.languages().clone());
language_registry.add(rust_lang()); language_registry.add(rust_lang());
@ -7466,7 +7469,9 @@ async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
); );
let buffer = project let buffer = project
.update(cx, |project, cx| project.open_local_buffer("/file.rs", cx)) .update(cx, |project, cx| {
project.open_local_buffer(path!("/file.rs"), cx)
})
.await .await
.unwrap(); .unwrap();
@ -7491,7 +7496,7 @@ async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
.handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move { .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
assert_eq!( assert_eq!(
params.text_document.uri, params.text_document.uri,
lsp::Url::from_file_path("/file.rs").unwrap() lsp::Url::from_file_path(path!("/file.rs")).unwrap()
); );
assert_eq!(params.options.tab_size, 4); assert_eq!(params.options.tab_size, 4);
Ok(Some(vec![lsp::TextEdit::new( Ok(Some(vec![lsp::TextEdit::new(
@ -7519,7 +7524,7 @@ async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
move |params, _| async move { move |params, _| async move {
assert_eq!( assert_eq!(
params.text_document.uri, params.text_document.uri,
lsp::Url::from_file_path("/file.rs").unwrap() lsp::Url::from_file_path(path!("/file.rs")).unwrap()
); );
futures::future::pending::<()>().await; futures::future::pending::<()>().await;
unreachable!() unreachable!()
@ -7577,7 +7582,7 @@ async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
.handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move { .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
assert_eq!( assert_eq!(
params.text_document.uri, params.text_document.uri,
lsp::Url::from_file_path("/file.rs").unwrap() lsp::Url::from_file_path(path!("/file.rs")).unwrap()
); );
assert_eq!(params.options.tab_size, 8); assert_eq!(params.options.tab_size, 8);
Ok(Some(vec![])) Ok(Some(vec![]))
@ -7597,9 +7602,9 @@ async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
}); });
let fs = FakeFs::new(cx.executor()); let fs = FakeFs::new(cx.executor());
fs.insert_file("/file.rs", Default::default()).await; fs.insert_file(path!("/file.rs"), Default::default()).await;
let project = Project::test(fs, ["/file.rs".as_ref()], cx).await; let project = Project::test(fs, [path!("/").as_ref()], cx).await;
let language_registry = project.read_with(cx, |project, _| project.languages().clone()); let language_registry = project.read_with(cx, |project, _| project.languages().clone());
language_registry.add(Arc::new(Language::new( language_registry.add(Arc::new(Language::new(
@ -7633,7 +7638,9 @@ async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
); );
let buffer = project let buffer = project
.update(cx, |project, cx| project.open_local_buffer("/file.rs", cx)) .update(cx, |project, cx| {
project.open_local_buffer(path!("/file.rs"), cx)
})
.await .await
.unwrap(); .unwrap();
@ -7663,7 +7670,7 @@ async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move { .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
assert_eq!( assert_eq!(
params.text_document.uri, params.text_document.uri,
lsp::Url::from_file_path("/file.rs").unwrap() lsp::Url::from_file_path(path!("/file.rs")).unwrap()
); );
assert_eq!(params.options.tab_size, 4); assert_eq!(params.options.tab_size, 4);
Ok(Some(vec![lsp::TextEdit::new( Ok(Some(vec![lsp::TextEdit::new(
@ -7687,7 +7694,7 @@ async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move { fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
assert_eq!( assert_eq!(
params.text_document.uri, params.text_document.uri,
lsp::Url::from_file_path("/file.rs").unwrap() lsp::Url::from_file_path(path!("/file.rs")).unwrap()
); );
futures::future::pending::<()>().await; futures::future::pending::<()>().await;
unreachable!() unreachable!()
@ -8727,14 +8734,14 @@ async fn test_multiline_completion(cx: &mut gpui::TestAppContext) {
let fs = FakeFs::new(cx.executor()); let fs = FakeFs::new(cx.executor());
fs.insert_tree( fs.insert_tree(
"/a", path!("/a"),
json!({ json!({
"main.ts": "a", "main.ts": "a",
}), }),
) )
.await; .await;
let project = Project::test(fs, ["/a".as_ref()], cx).await; let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
let language_registry = project.read_with(cx, |project, _| project.languages().clone()); let language_registry = project.read_with(cx, |project, _| project.languages().clone());
let typescript_language = Arc::new(Language::new( let typescript_language = Arc::new(Language::new(
LanguageConfig { LanguageConfig {
@ -8794,7 +8801,7 @@ async fn test_multiline_completion(cx: &mut gpui::TestAppContext) {
.unwrap(); .unwrap();
let _buffer = project let _buffer = project
.update(cx, |project, cx| { .update(cx, |project, cx| {
project.open_local_buffer_with_lsp("/a/main.ts", cx) project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
}) })
.await .await
.unwrap(); .unwrap();
@ -10570,7 +10577,7 @@ async fn go_to_prev_overlapping_diagnostic(
.update_diagnostics( .update_diagnostics(
LanguageServerId(0), LanguageServerId(0),
lsp::PublishDiagnosticsParams { lsp::PublishDiagnosticsParams {
uri: lsp::Url::from_file_path("/root/file").unwrap(), uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
version: None, version: None,
diagnostics: vec![ diagnostics: vec![
lsp::Diagnostic { lsp::Diagnostic {
@ -10663,7 +10670,7 @@ async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
lsp_store.update_diagnostics( lsp_store.update_diagnostics(
LanguageServerId(0), LanguageServerId(0),
lsp::PublishDiagnosticsParams { lsp::PublishDiagnosticsParams {
uri: lsp::Url::from_file_path("/root/file").unwrap(), uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
version: None, version: None,
diagnostics: vec![lsp::Diagnostic { diagnostics: vec![lsp::Diagnostic {
range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 12)), range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 12)),
@ -10923,14 +10930,14 @@ async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
let fs = FakeFs::new(cx.executor()); let fs = FakeFs::new(cx.executor());
fs.insert_tree( fs.insert_tree(
"/a", path!("/a"),
json!({ json!({
"main.rs": "fn main() { let a = 5; }", "main.rs": "fn main() { let a = 5; }",
"other.rs": "// Test file", "other.rs": "// Test file",
}), }),
) )
.await; .await;
let project = Project::test(fs, ["/a".as_ref()], cx).await; let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
let language_registry = project.read_with(cx, |project, _| project.languages().clone()); let language_registry = project.read_with(cx, |project, _| project.languages().clone());
language_registry.add(Arc::new(Language::new( language_registry.add(Arc::new(Language::new(
@ -10982,7 +10989,7 @@ async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
let buffer = project let buffer = project
.update(cx, |project, cx| { .update(cx, |project, cx| {
project.open_local_buffer("/a/main.rs", cx) project.open_local_buffer(path!("/a/main.rs"), cx)
}) })
.await .await
.unwrap(); .unwrap();
@ -11002,7 +11009,7 @@ async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move { fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
assert_eq!( assert_eq!(
params.text_document_position.text_document.uri, params.text_document_position.text_document.uri,
lsp::Url::from_file_path("/a/main.rs").unwrap(), lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
); );
assert_eq!( assert_eq!(
params.text_document_position.position, params.text_document_position.position,
@ -11040,7 +11047,7 @@ async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::Test
let fs = FakeFs::new(cx.executor()); let fs = FakeFs::new(cx.executor());
fs.insert_tree( fs.insert_tree(
"/a", path!("/a"),
json!({ json!({
"main.rs": "fn main() { let a = 5; }", "main.rs": "fn main() { let a = 5; }",
"other.rs": "// Test file", "other.rs": "// Test file",
@ -11048,7 +11055,7 @@ async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::Test
) )
.await; .await;
let project = Project::test(fs, ["/a".as_ref()], cx).await; let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
let server_restarts = Arc::new(AtomicUsize::new(0)); let server_restarts = Arc::new(AtomicUsize::new(0));
let closure_restarts = Arc::clone(&server_restarts); let closure_restarts = Arc::clone(&server_restarts);
@ -11088,7 +11095,7 @@ async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::Test
let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx)); let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
let _buffer = project let _buffer = project
.update(cx, |project, cx| { .update(cx, |project, cx| {
project.open_local_buffer_with_lsp("/a/main.rs", cx) project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
}) })
.await .await
.unwrap(); .unwrap();
@ -11861,9 +11868,9 @@ async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
}); });
let fs = FakeFs::new(cx.executor()); let fs = FakeFs::new(cx.executor());
fs.insert_file("/file.ts", Default::default()).await; fs.insert_file(path!("/file.ts"), Default::default()).await;
let project = Project::test(fs, ["/file.ts".as_ref()], cx).await; let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
let language_registry = project.read_with(cx, |project, _| project.languages().clone()); let language_registry = project.read_with(cx, |project, _| project.languages().clone());
language_registry.add(Arc::new(Language::new( language_registry.add(Arc::new(Language::new(
@ -11895,7 +11902,9 @@ async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX; let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
let buffer = project let buffer = project
.update(cx, |project, cx| project.open_local_buffer("/file.ts", cx)) .update(cx, |project, cx| {
project.open_local_buffer(path!("/file.ts"), cx)
})
.await .await
.unwrap(); .unwrap();

View file

@ -560,7 +560,7 @@ mod tests {
use settings::SettingsStore; use settings::SettingsStore;
use std::{cmp, env, ops::Range, path::Path}; use std::{cmp, env, ops::Range, path::Path};
use unindent::Unindent as _; use unindent::Unindent as _;
use util::RandomCharIter; use util::{path, RandomCharIter};
// macro_rules! assert_blame_rows { // macro_rules! assert_blame_rows {
// ($blame:expr, $rows:expr, $expected:expr, $cx:expr) => { // ($blame:expr, $rows:expr, $expected:expr, $cx:expr) => {
@ -793,7 +793,7 @@ mod tests {
let fs = FakeFs::new(cx.executor()); let fs = FakeFs::new(cx.executor());
fs.insert_tree( fs.insert_tree(
"/my-repo", path!("/my-repo"),
json!({ json!({
".git": {}, ".git": {},
"file.txt": r#" "file.txt": r#"
@ -807,7 +807,7 @@ mod tests {
.await; .await;
fs.set_blame_for_repo( fs.set_blame_for_repo(
Path::new("/my-repo/.git"), Path::new(path!("/my-repo/.git")),
vec![( vec![(
"file.txt".into(), "file.txt".into(),
Blame { Blame {
@ -817,10 +817,10 @@ mod tests {
)], )],
); );
let project = Project::test(fs, ["/my-repo".as_ref()], cx).await; let project = Project::test(fs, [path!("/my-repo").as_ref()], cx).await;
let buffer = project let buffer = project
.update(cx, |project, cx| { .update(cx, |project, cx| {
project.open_local_buffer("/my-repo/file.txt", cx) project.open_local_buffer(path!("/my-repo/file.txt"), cx)
}) })
.await .await
.unwrap(); .unwrap();
@ -945,7 +945,7 @@ mod tests {
log::info!("initial buffer text: {:?}", buffer_initial_text); log::info!("initial buffer text: {:?}", buffer_initial_text);
fs.insert_tree( fs.insert_tree(
"/my-repo", path!("/my-repo"),
json!({ json!({
".git": {}, ".git": {},
"file.txt": buffer_initial_text.to_string() "file.txt": buffer_initial_text.to_string()
@ -956,7 +956,7 @@ mod tests {
let blame_entries = gen_blame_entries(buffer_initial_text.max_point().row, &mut rng); let blame_entries = gen_blame_entries(buffer_initial_text.max_point().row, &mut rng);
log::info!("initial blame entries: {:?}", blame_entries); log::info!("initial blame entries: {:?}", blame_entries);
fs.set_blame_for_repo( fs.set_blame_for_repo(
Path::new("/my-repo/.git"), Path::new(path!("/my-repo/.git")),
vec![( vec![(
"file.txt".into(), "file.txt".into(),
Blame { Blame {
@ -966,10 +966,10 @@ mod tests {
)], )],
); );
let project = Project::test(fs.clone(), ["/my-repo".as_ref()], cx).await; let project = Project::test(fs.clone(), [path!("/my-repo").as_ref()], cx).await;
let buffer = project let buffer = project
.update(cx, |project, cx| { .update(cx, |project, cx| {
project.open_local_buffer("/my-repo/file.txt", cx) project.open_local_buffer(path!("/my-repo/file.txt"), cx)
}) })
.await .await
.unwrap(); .unwrap();
@ -998,7 +998,7 @@ mod tests {
log::info!("regenerating blame entries: {:?}", blame_entries); log::info!("regenerating blame entries: {:?}", blame_entries);
fs.set_blame_for_repo( fs.set_blame_for_repo(
Path::new("/my-repo/.git"), Path::new(path!("/my-repo/.git")),
vec![( vec![(
"file.txt".into(), "file.txt".into(),
Blame { Blame {

View file

@ -921,7 +921,7 @@ mod tests {
use indoc::indoc; use indoc::indoc;
use language::language_settings::InlayHintSettings; use language::language_settings::InlayHintSettings;
use lsp::request::{GotoDefinition, GotoTypeDefinition}; use lsp::request::{GotoDefinition, GotoTypeDefinition};
use util::assert_set_eq; use util::{assert_set_eq, path};
use workspace::item::Item; use workspace::item::Item;
#[gpui::test] #[gpui::test]
@ -1574,18 +1574,31 @@ mod tests {
// Insert a new file // Insert a new file
let fs = cx.update_workspace(|workspace, _, cx| workspace.project().read(cx).fs().clone()); let fs = cx.update_workspace(|workspace, _, cx| workspace.project().read(cx).fs().clone());
fs.as_fake() fs.as_fake()
.insert_file("/root/dir/file2.rs", "This is file2.rs".as_bytes().to_vec()) .insert_file(
path!("/root/dir/file2.rs"),
"This is file2.rs".as_bytes().to_vec(),
)
.await; .await;
#[cfg(not(target_os = "windows"))]
cx.set_state(indoc! {" cx.set_state(indoc! {"
You can't go to a file that does_not_exist.txt. You can't go to a file that does_not_exist.txt.
Go to file2.rs if you want. Go to file2.rs if you want.
Or go to ../dir/file2.rs if you want. Or go to ../dir/file2.rs if you want.
Or go to /root/dir/file2.rs if project is local. Or go to /root/dir/file2.rs if project is local.
Or go to /root/dir/file2 if this is a Rust file.ˇ Or go to /root/dir/file2 if this is a Rust file.ˇ
"});
#[cfg(target_os = "windows")]
cx.set_state(indoc! {"
You can't go to a file that does_not_exist.txt.
Go to file2.rs if you want.
Or go to ../dir/file2.rs if you want.
Or go to C:/root/dir/file2.rs if project is local.
Or go to C:/root/dir/file2 if this is a Rust file.ˇ
"}); "});
// File does not exist // File does not exist
#[cfg(not(target_os = "windows"))]
let screen_coord = cx.pixel_position(indoc! {" let screen_coord = cx.pixel_position(indoc! {"
You can't go to a file that dˇoes_not_exist.txt. You can't go to a file that dˇoes_not_exist.txt.
Go to file2.rs if you want. Go to file2.rs if you want.
@ -1593,6 +1606,14 @@ mod tests {
Or go to /root/dir/file2.rs if project is local. Or go to /root/dir/file2.rs if project is local.
Or go to /root/dir/file2 if this is a Rust file. Or go to /root/dir/file2 if this is a Rust file.
"}); "});
#[cfg(target_os = "windows")]
let screen_coord = cx.pixel_position(indoc! {"
You can't go to a file that dˇoes_not_exist.txt.
Go to file2.rs if you want.
Or go to ../dir/file2.rs if you want.
Or go to C:/root/dir/file2.rs if project is local.
Or go to C:/root/dir/file2 if this is a Rust file.
"});
cx.simulate_mouse_move(screen_coord, None, Modifiers::secondary_key()); cx.simulate_mouse_move(screen_coord, None, Modifiers::secondary_key());
// No highlight // No highlight
cx.update_editor(|editor, window, cx| { cx.update_editor(|editor, window, cx| {
@ -1605,6 +1626,7 @@ mod tests {
}); });
// Moving the mouse over a file that does exist should highlight it. // Moving the mouse over a file that does exist should highlight it.
#[cfg(not(target_os = "windows"))]
let screen_coord = cx.pixel_position(indoc! {" let screen_coord = cx.pixel_position(indoc! {"
You can't go to a file that does_not_exist.txt. You can't go to a file that does_not_exist.txt.
Go to fˇile2.rs if you want. Go to fˇile2.rs if you want.
@ -1612,8 +1634,17 @@ mod tests {
Or go to /root/dir/file2.rs if project is local. Or go to /root/dir/file2.rs if project is local.
Or go to /root/dir/file2 if this is a Rust file. Or go to /root/dir/file2 if this is a Rust file.
"}); "});
#[cfg(target_os = "windows")]
let screen_coord = cx.pixel_position(indoc! {"
You can't go to a file that does_not_exist.txt.
Go to fˇile2.rs if you want.
Or go to ../dir/file2.rs if you want.
Or go to C:/root/dir/file2.rs if project is local.
Or go to C:/root/dir/file2 if this is a Rust file.
"});
cx.simulate_mouse_move(screen_coord, None, Modifiers::secondary_key()); cx.simulate_mouse_move(screen_coord, None, Modifiers::secondary_key());
#[cfg(not(target_os = "windows"))]
cx.assert_editor_text_highlights::<HoveredLinkState>(indoc! {" cx.assert_editor_text_highlights::<HoveredLinkState>(indoc! {"
You can't go to a file that does_not_exist.txt. You can't go to a file that does_not_exist.txt.
Go to «file2.rsˇ» if you want. Go to «file2.rsˇ» if you want.
@ -1621,8 +1652,17 @@ mod tests {
Or go to /root/dir/file2.rs if project is local. Or go to /root/dir/file2.rs if project is local.
Or go to /root/dir/file2 if this is a Rust file. Or go to /root/dir/file2 if this is a Rust file.
"}); "});
#[cfg(target_os = "windows")]
cx.assert_editor_text_highlights::<HoveredLinkState>(indoc! {"
You can't go to a file that does_not_exist.txt.
Go to «file2.rsˇ» if you want.
Or go to ../dir/file2.rs if you want.
Or go to C:/root/dir/file2.rs if project is local.
Or go to C:/root/dir/file2 if this is a Rust file.
"});
// Moving the mouse over a relative path that does exist should highlight it // Moving the mouse over a relative path that does exist should highlight it
#[cfg(not(target_os = "windows"))]
let screen_coord = cx.pixel_position(indoc! {" let screen_coord = cx.pixel_position(indoc! {"
You can't go to a file that does_not_exist.txt. You can't go to a file that does_not_exist.txt.
Go to file2.rs if you want. Go to file2.rs if you want.
@ -1630,8 +1670,17 @@ mod tests {
Or go to /root/dir/file2.rs if project is local. Or go to /root/dir/file2.rs if project is local.
Or go to /root/dir/file2 if this is a Rust file. Or go to /root/dir/file2 if this is a Rust file.
"}); "});
#[cfg(target_os = "windows")]
let screen_coord = cx.pixel_position(indoc! {"
You can't go to a file that does_not_exist.txt.
Go to file2.rs if you want.
Or go to ../dir/fˇile2.rs if you want.
Or go to C:/root/dir/file2.rs if project is local.
Or go to C:/root/dir/file2 if this is a Rust file.
"});
cx.simulate_mouse_move(screen_coord, None, Modifiers::secondary_key()); cx.simulate_mouse_move(screen_coord, None, Modifiers::secondary_key());
#[cfg(not(target_os = "windows"))]
cx.assert_editor_text_highlights::<HoveredLinkState>(indoc! {" cx.assert_editor_text_highlights::<HoveredLinkState>(indoc! {"
You can't go to a file that does_not_exist.txt. You can't go to a file that does_not_exist.txt.
Go to file2.rs if you want. Go to file2.rs if you want.
@ -1639,8 +1688,17 @@ mod tests {
Or go to /root/dir/file2.rs if project is local. Or go to /root/dir/file2.rs if project is local.
Or go to /root/dir/file2 if this is a Rust file. Or go to /root/dir/file2 if this is a Rust file.
"}); "});
#[cfg(target_os = "windows")]
cx.assert_editor_text_highlights::<HoveredLinkState>(indoc! {"
You can't go to a file that does_not_exist.txt.
Go to file2.rs if you want.
Or go to «../dir/file2.rsˇ» if you want.
Or go to C:/root/dir/file2.rs if project is local.
Or go to C:/root/dir/file2 if this is a Rust file.
"});
// Moving the mouse over an absolute path that does exist should highlight it // Moving the mouse over an absolute path that does exist should highlight it
#[cfg(not(target_os = "windows"))]
let screen_coord = cx.pixel_position(indoc! {" let screen_coord = cx.pixel_position(indoc! {"
You can't go to a file that does_not_exist.txt. You can't go to a file that does_not_exist.txt.
Go to file2.rs if you want. Go to file2.rs if you want.
@ -1649,7 +1707,17 @@ mod tests {
Or go to /root/dir/file2 if this is a Rust file. Or go to /root/dir/file2 if this is a Rust file.
"}); "});
#[cfg(target_os = "windows")]
let screen_coord = cx.pixel_position(indoc! {"
You can't go to a file that does_not_exist.txt.
Go to file2.rs if you want.
Or go to ../dir/file2.rs if you want.
Or go to C:/root/diˇr/file2.rs if project is local.
Or go to C:/root/dir/file2 if this is a Rust file.
"});
cx.simulate_mouse_move(screen_coord, None, Modifiers::secondary_key()); cx.simulate_mouse_move(screen_coord, None, Modifiers::secondary_key());
#[cfg(not(target_os = "windows"))]
cx.assert_editor_text_highlights::<HoveredLinkState>(indoc! {" cx.assert_editor_text_highlights::<HoveredLinkState>(indoc! {"
You can't go to a file that does_not_exist.txt. You can't go to a file that does_not_exist.txt.
Go to file2.rs if you want. Go to file2.rs if you want.
@ -1657,8 +1725,17 @@ mod tests {
Or go to «/root/dir/file2.rsˇ» if project is local. Or go to «/root/dir/file2.rsˇ» if project is local.
Or go to /root/dir/file2 if this is a Rust file. Or go to /root/dir/file2 if this is a Rust file.
"}); "});
#[cfg(target_os = "windows")]
cx.assert_editor_text_highlights::<HoveredLinkState>(indoc! {"
You can't go to a file that does_not_exist.txt.
Go to file2.rs if you want.
Or go to ../dir/file2.rs if you want.
Or go to «C:/root/dir/file2.rsˇ» if project is local.
Or go to C:/root/dir/file2 if this is a Rust file.
"});
// Moving the mouse over a path that exists, if we add the language-specific suffix, it should highlight it // Moving the mouse over a path that exists, if we add the language-specific suffix, it should highlight it
#[cfg(not(target_os = "windows"))]
let screen_coord = cx.pixel_position(indoc! {" let screen_coord = cx.pixel_position(indoc! {"
You can't go to a file that does_not_exist.txt. You can't go to a file that does_not_exist.txt.
Go to file2.rs if you want. Go to file2.rs if you want.
@ -1666,8 +1743,17 @@ mod tests {
Or go to /root/dir/file2.rs if project is local. Or go to /root/dir/file2.rs if project is local.
Or go to /root/diˇr/file2 if this is a Rust file. Or go to /root/diˇr/file2 if this is a Rust file.
"}); "});
#[cfg(target_os = "windows")]
let screen_coord = cx.pixel_position(indoc! {"
You can't go to a file that does_not_exist.txt.
Go to file2.rs if you want.
Or go to ../dir/file2.rs if you want.
Or go to C:/root/dir/file2.rs if project is local.
Or go to C:/root/diˇr/file2 if this is a Rust file.
"});
cx.simulate_mouse_move(screen_coord, None, Modifiers::secondary_key()); cx.simulate_mouse_move(screen_coord, None, Modifiers::secondary_key());
#[cfg(not(target_os = "windows"))]
cx.assert_editor_text_highlights::<HoveredLinkState>(indoc! {" cx.assert_editor_text_highlights::<HoveredLinkState>(indoc! {"
You can't go to a file that does_not_exist.txt. You can't go to a file that does_not_exist.txt.
Go to file2.rs if you want. Go to file2.rs if you want.
@ -1675,6 +1761,14 @@ mod tests {
Or go to /root/dir/file2.rs if project is local. Or go to /root/dir/file2.rs if project is local.
Or go to «/root/dir/file2ˇ» if this is a Rust file. Or go to «/root/dir/file2ˇ» if this is a Rust file.
"}); "});
#[cfg(target_os = "windows")]
cx.assert_editor_text_highlights::<HoveredLinkState>(indoc! {"
You can't go to a file that does_not_exist.txt.
Go to file2.rs if you want.
Or go to ../dir/file2.rs if you want.
Or go to C:/root/dir/file2.rs if project is local.
Or go to «C:/root/dir/file2ˇ» if this is a Rust file.
"});
cx.simulate_click(screen_coord, Modifiers::secondary_key()); cx.simulate_click(screen_coord, Modifiers::secondary_key());
@ -1692,7 +1786,10 @@ mod tests {
let file = buffer.read(cx).file().unwrap(); let file = buffer.read(cx).file().unwrap();
let file_path = file.as_local().unwrap().abs_path(cx); let file_path = file.as_local().unwrap().abs_path(cx);
assert_eq!(file_path.to_str().unwrap(), "/root/dir/file2.rs"); assert_eq!(
file_path,
std::path::PathBuf::from(path!("/root/dir/file2.rs"))
);
}); });
} }

View file

@ -1274,6 +1274,7 @@ pub mod tests {
use settings::SettingsStore; use settings::SettingsStore;
use std::sync::atomic::{AtomicBool, AtomicU32, AtomicUsize, Ordering}; use std::sync::atomic::{AtomicBool, AtomicU32, AtomicUsize, Ordering};
use text::Point; use text::Point;
use util::path;
use super::*; use super::*;
@ -1499,7 +1500,7 @@ pub mod tests {
let fs = FakeFs::new(cx.background_executor.clone()); let fs = FakeFs::new(cx.background_executor.clone());
fs.insert_tree( fs.insert_tree(
"/a", path!("/a"),
json!({ json!({
"main.rs": "fn main() { a } // and some long comment to ensure inlays are not trimmed out", "main.rs": "fn main() { a } // and some long comment to ensure inlays are not trimmed out",
"other.md": "Test md file with some text", "other.md": "Test md file with some text",
@ -1507,7 +1508,7 @@ pub mod tests {
) )
.await; .await;
let project = Project::test(fs, ["/a".as_ref()], cx).await; let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
let language_registry = project.read_with(cx, |project, _| project.languages().clone()); let language_registry = project.read_with(cx, |project, _| project.languages().clone());
let mut rs_fake_servers = None; let mut rs_fake_servers = None;
@ -1542,14 +1543,16 @@ pub mod tests {
"Rust" => { "Rust" => {
assert_eq!( assert_eq!(
params.text_document.uri, params.text_document.uri,
lsp::Url::from_file_path("/a/main.rs").unwrap(), lsp::Url::from_file_path(path!("/a/main.rs"))
.unwrap(),
); );
rs_lsp_request_count.fetch_add(1, Ordering::Release) + 1 rs_lsp_request_count.fetch_add(1, Ordering::Release) + 1
} }
"Markdown" => { "Markdown" => {
assert_eq!( assert_eq!(
params.text_document.uri, params.text_document.uri,
lsp::Url::from_file_path("/a/other.md").unwrap(), lsp::Url::from_file_path(path!("/a/other.md"))
.unwrap(),
); );
md_lsp_request_count.fetch_add(1, Ordering::Release) + 1 md_lsp_request_count.fetch_add(1, Ordering::Release) + 1
} }
@ -1585,7 +1588,7 @@ pub mod tests {
let rs_buffer = project let rs_buffer = project
.update(cx, |project, cx| { .update(cx, |project, cx| {
project.open_local_buffer("/a/main.rs", cx) project.open_local_buffer(path!("/a/main.rs"), cx)
}) })
.await .await
.unwrap(); .unwrap();
@ -1611,7 +1614,7 @@ pub mod tests {
cx.executor().run_until_parked(); cx.executor().run_until_parked();
let md_buffer = project let md_buffer = project
.update(cx, |project, cx| { .update(cx, |project, cx| {
project.open_local_buffer("/a/other.md", cx) project.open_local_buffer(path!("/a/other.md"), cx)
}) })
.await .await
.unwrap(); .unwrap();
@ -2173,7 +2176,7 @@ pub mod tests {
let fs = FakeFs::new(cx.background_executor.clone()); let fs = FakeFs::new(cx.background_executor.clone());
fs.insert_tree( fs.insert_tree(
"/a", path!("/a"),
json!({ json!({
"main.rs": format!("fn main() {{\n{}\n}}", "let i = 5;\n".repeat(500)), "main.rs": format!("fn main() {{\n{}\n}}", "let i = 5;\n".repeat(500)),
"other.rs": "// Test file", "other.rs": "// Test file",
@ -2181,7 +2184,7 @@ pub mod tests {
) )
.await; .await;
let project = Project::test(fs, ["/a".as_ref()], cx).await; let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
let language_registry = project.read_with(cx, |project, _| project.languages().clone()); let language_registry = project.read_with(cx, |project, _| project.languages().clone());
language_registry.add(rust_lang()); language_registry.add(rust_lang());
@ -2209,7 +2212,7 @@ pub mod tests {
async move { async move {
assert_eq!( assert_eq!(
params.text_document.uri, params.text_document.uri,
lsp::Url::from_file_path("/a/main.rs").unwrap(), lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
); );
task_lsp_request_ranges.lock().push(params.range); task_lsp_request_ranges.lock().push(params.range);
@ -2237,7 +2240,7 @@ pub mod tests {
let buffer = project let buffer = project
.update(cx, |project, cx| { .update(cx, |project, cx| {
project.open_local_buffer("/a/main.rs", cx) project.open_local_buffer(path!("/a/main.rs"), cx)
}) })
.await .await
.unwrap(); .unwrap();
@ -2471,7 +2474,7 @@ pub mod tests {
let fs = FakeFs::new(cx.background_executor.clone()); let fs = FakeFs::new(cx.background_executor.clone());
fs.insert_tree( fs.insert_tree(
"/a", path!("/a"),
json!({ json!({
"main.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|i| format!("let i = {i};\n")).collect::<Vec<_>>().join("")), "main.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|i| format!("let i = {i};\n")).collect::<Vec<_>>().join("")),
"other.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|j| format!("let j = {j};\n")).collect::<Vec<_>>().join("")), "other.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|j| format!("let j = {j};\n")).collect::<Vec<_>>().join("")),
@ -2479,7 +2482,7 @@ pub mod tests {
) )
.await; .await;
let project = Project::test(fs, ["/a".as_ref()], cx).await; let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
let language_registry = project.read_with(cx, |project, _| project.languages().clone()); let language_registry = project.read_with(cx, |project, _| project.languages().clone());
let language = rust_lang(); let language = rust_lang();
@ -2497,13 +2500,13 @@ pub mod tests {
let (buffer_1, _handle1) = project let (buffer_1, _handle1) = project
.update(cx, |project, cx| { .update(cx, |project, cx| {
project.open_local_buffer_with_lsp("/a/main.rs", cx) project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
}) })
.await .await
.unwrap(); .unwrap();
let (buffer_2, _handle2) = project let (buffer_2, _handle2) = project
.update(cx, |project, cx| { .update(cx, |project, cx| {
project.open_local_buffer_with_lsp("/a/other.rs", cx) project.open_local_buffer_with_lsp(path!("/a/other.rs"), cx)
}) })
.await .await
.unwrap(); .unwrap();
@ -2585,11 +2588,11 @@ pub mod tests {
let task_editor_edited = Arc::clone(&closure_editor_edited); let task_editor_edited = Arc::clone(&closure_editor_edited);
async move { async move {
let hint_text = if params.text_document.uri let hint_text = if params.text_document.uri
== lsp::Url::from_file_path("/a/main.rs").unwrap() == lsp::Url::from_file_path(path!("/a/main.rs")).unwrap()
{ {
"main hint" "main hint"
} else if params.text_document.uri } else if params.text_document.uri
== lsp::Url::from_file_path("/a/other.rs").unwrap() == lsp::Url::from_file_path(path!("/a/other.rs")).unwrap()
{ {
"other hint" "other hint"
} else { } else {
@ -2815,7 +2818,7 @@ pub mod tests {
let fs = FakeFs::new(cx.background_executor.clone()); let fs = FakeFs::new(cx.background_executor.clone());
fs.insert_tree( fs.insert_tree(
"/a", path!("/a"),
json!({ json!({
"main.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|i| format!("let i = {i};\n")).collect::<Vec<_>>().join("")), "main.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|i| format!("let i = {i};\n")).collect::<Vec<_>>().join("")),
"other.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|j| format!("let j = {j};\n")).collect::<Vec<_>>().join("")), "other.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|j| format!("let j = {j};\n")).collect::<Vec<_>>().join("")),
@ -2823,7 +2826,7 @@ pub mod tests {
) )
.await; .await;
let project = Project::test(fs, ["/a".as_ref()], cx).await; let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
let language_registry = project.read_with(cx, |project, _| project.languages().clone()); let language_registry = project.read_with(cx, |project, _| project.languages().clone());
language_registry.add(rust_lang()); language_registry.add(rust_lang());
@ -2840,13 +2843,13 @@ pub mod tests {
let (buffer_1, _handle) = project let (buffer_1, _handle) = project
.update(cx, |project, cx| { .update(cx, |project, cx| {
project.open_local_buffer_with_lsp("/a/main.rs", cx) project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
}) })
.await .await
.unwrap(); .unwrap();
let (buffer_2, _handle2) = project let (buffer_2, _handle2) = project
.update(cx, |project, cx| { .update(cx, |project, cx| {
project.open_local_buffer_with_lsp("/a/other.rs", cx) project.open_local_buffer_with_lsp(path!("/a/other.rs"), cx)
}) })
.await .await
.unwrap(); .unwrap();
@ -2886,11 +2889,11 @@ pub mod tests {
let task_editor_edited = Arc::clone(&closure_editor_edited); let task_editor_edited = Arc::clone(&closure_editor_edited);
async move { async move {
let hint_text = if params.text_document.uri let hint_text = if params.text_document.uri
== lsp::Url::from_file_path("/a/main.rs").unwrap() == lsp::Url::from_file_path(path!("/a/main.rs")).unwrap()
{ {
"main hint" "main hint"
} else if params.text_document.uri } else if params.text_document.uri
== lsp::Url::from_file_path("/a/other.rs").unwrap() == lsp::Url::from_file_path(path!("/a/other.rs")).unwrap()
{ {
"other hint" "other hint"
} else { } else {
@ -3027,7 +3030,7 @@ pub mod tests {
let fs = FakeFs::new(cx.background_executor.clone()); let fs = FakeFs::new(cx.background_executor.clone());
fs.insert_tree( fs.insert_tree(
"/a", path!("/a"),
json!({ json!({
"main.rs": format!(r#"fn main() {{\n{}\n}}"#, format!("let i = {};\n", "".repeat(10)).repeat(500)), "main.rs": format!(r#"fn main() {{\n{}\n}}"#, format!("let i = {};\n", "".repeat(10)).repeat(500)),
"other.rs": "// Test file", "other.rs": "// Test file",
@ -3035,7 +3038,7 @@ pub mod tests {
) )
.await; .await;
let project = Project::test(fs, ["/a".as_ref()], cx).await; let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
let language_registry = project.read_with(cx, |project, _| project.languages().clone()); let language_registry = project.read_with(cx, |project, _| project.languages().clone());
language_registry.add(rust_lang()); language_registry.add(rust_lang());
@ -3054,7 +3057,7 @@ pub mod tests {
async move { async move {
assert_eq!( assert_eq!(
params.text_document.uri, params.text_document.uri,
lsp::Url::from_file_path("/a/main.rs").unwrap(), lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
); );
let query_start = params.range.start; let query_start = params.range.start;
Ok(Some(vec![lsp::InlayHint { Ok(Some(vec![lsp::InlayHint {
@ -3077,7 +3080,7 @@ pub mod tests {
let buffer = project let buffer = project
.update(cx, |project, cx| { .update(cx, |project, cx| {
project.open_local_buffer("/a/main.rs", cx) project.open_local_buffer(path!("/a/main.rs"), cx)
}) })
.await .await
.unwrap(); .unwrap();
@ -3250,7 +3253,7 @@ pub mod tests {
let fs = FakeFs::new(cx.background_executor.clone()); let fs = FakeFs::new(cx.background_executor.clone());
fs.insert_tree( fs.insert_tree(
"/a", path!("/a"),
json!({ json!({
"main.rs": "fn main() { "main.rs": "fn main() {
let x = 42; let x = 42;
@ -3265,7 +3268,7 @@ pub mod tests {
) )
.await; .await;
let project = Project::test(fs, ["/a".as_ref()], cx).await; let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
let language_registry = project.read_with(cx, |project, _| project.languages().clone()); let language_registry = project.read_with(cx, |project, _| project.languages().clone());
language_registry.add(rust_lang()); language_registry.add(rust_lang());
@ -3281,7 +3284,7 @@ pub mod tests {
move |params, _| async move { move |params, _| async move {
assert_eq!( assert_eq!(
params.text_document.uri, params.text_document.uri,
lsp::Url::from_file_path("/a/main.rs").unwrap(), lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
); );
Ok(Some( Ok(Some(
serde_json::from_value(json!([ serde_json::from_value(json!([
@ -3351,7 +3354,7 @@ pub mod tests {
let buffer = project let buffer = project
.update(cx, |project, cx| { .update(cx, |project, cx| {
project.open_local_buffer("/a/main.rs", cx) project.open_local_buffer(path!("/a/main.rs"), cx)
}) })
.await .await
.unwrap(); .unwrap();
@ -3408,7 +3411,7 @@ pub mod tests {
) -> (&'static str, WindowHandle<Editor>, FakeLanguageServer) { ) -> (&'static str, WindowHandle<Editor>, FakeLanguageServer) {
let fs = FakeFs::new(cx.background_executor.clone()); let fs = FakeFs::new(cx.background_executor.clone());
fs.insert_tree( fs.insert_tree(
"/a", path!("/a"),
json!({ json!({
"main.rs": "fn main() { a } // and some long comment to ensure inlays are not trimmed out", "main.rs": "fn main() { a } // and some long comment to ensure inlays are not trimmed out",
"other.rs": "// Test file", "other.rs": "// Test file",
@ -3416,8 +3419,8 @@ pub mod tests {
) )
.await; .await;
let project = Project::test(fs, ["/a".as_ref()], cx).await; let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
let file_path = "/a/main.rs"; let file_path = path!("/a/main.rs");
let language_registry = project.read_with(cx, |project, _| project.languages().clone()); let language_registry = project.read_with(cx, |project, _| project.languages().clone());
language_registry.add(rust_lang()); language_registry.add(rust_lang());
@ -3435,7 +3438,7 @@ pub mod tests {
let buffer = project let buffer = project
.update(cx, |project, cx| { .update(cx, |project, cx| {
project.open_local_buffer("/a/main.rs", cx) project.open_local_buffer(path!("/a/main.rs"), cx)
}) })
.await .await
.unwrap(); .unwrap();

View file

@ -1717,6 +1717,7 @@ mod tests {
use language::{LanguageMatcher, TestFile}; use language::{LanguageMatcher, TestFile};
use project::FakeFs; use project::FakeFs;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use util::path;
#[gpui::test] #[gpui::test]
fn test_path_for_file(cx: &mut App) { fn test_path_for_file(cx: &mut App) {
@ -1771,24 +1772,24 @@ mod tests {
init_test(cx, |_| {}); init_test(cx, |_| {});
let fs = FakeFs::new(cx.executor()); let fs = FakeFs::new(cx.executor());
fs.insert_file("/file.rs", Default::default()).await; fs.insert_file(path!("/file.rs"), Default::default()).await;
// Test case 1: Deserialize with path and contents // Test case 1: Deserialize with path and contents
{ {
let project = Project::test(fs.clone(), ["/file.rs".as_ref()], cx).await; let project = Project::test(fs.clone(), [path!("/file.rs").as_ref()], cx).await;
let (workspace, cx) = let (workspace, cx) =
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx)); cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
let workspace_id = workspace::WORKSPACE_DB.next_id().await.unwrap(); let workspace_id = workspace::WORKSPACE_DB.next_id().await.unwrap();
let item_id = 1234 as ItemId; let item_id = 1234 as ItemId;
let mtime = fs let mtime = fs
.metadata(Path::new("/file.rs")) .metadata(Path::new(path!("/file.rs")))
.await .await
.unwrap() .unwrap()
.unwrap() .unwrap()
.mtime; .mtime;
let serialized_editor = SerializedEditor { let serialized_editor = SerializedEditor {
abs_path: Some(PathBuf::from("/file.rs")), abs_path: Some(PathBuf::from(path!("/file.rs"))),
contents: Some("fn main() {}".to_string()), contents: Some("fn main() {}".to_string()),
language: Some("Rust".to_string()), language: Some("Rust".to_string()),
mtime: Some(mtime), mtime: Some(mtime),
@ -1812,7 +1813,7 @@ mod tests {
// Test case 2: Deserialize with only path // Test case 2: Deserialize with only path
{ {
let project = Project::test(fs.clone(), ["/file.rs".as_ref()], cx).await; let project = Project::test(fs.clone(), [path!("/file.rs").as_ref()], cx).await;
let (workspace, cx) = let (workspace, cx) =
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx)); cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
@ -1820,7 +1821,7 @@ mod tests {
let item_id = 5678 as ItemId; let item_id = 5678 as ItemId;
let serialized_editor = SerializedEditor { let serialized_editor = SerializedEditor {
abs_path: Some(PathBuf::from("/file.rs")), abs_path: Some(PathBuf::from(path!("/file.rs"))),
contents: None, contents: None,
language: None, language: None,
mtime: None, mtime: None,
@ -1845,7 +1846,7 @@ mod tests {
// Test case 3: Deserialize with no path (untitled buffer, with content and language) // Test case 3: Deserialize with no path (untitled buffer, with content and language)
{ {
let project = Project::test(fs.clone(), ["/file.rs".as_ref()], cx).await; let project = Project::test(fs.clone(), [path!("/file.rs").as_ref()], cx).await;
// Add Rust to the language, so that we can restore the language of the buffer // Add Rust to the language, so that we can restore the language of the buffer
project.update(cx, |project, _| project.languages().add(rust_language())); project.update(cx, |project, _| project.languages().add(rust_language()));
@ -1884,7 +1885,7 @@ mod tests {
// Test case 4: Deserialize with path, content, and old mtime // Test case 4: Deserialize with path, content, and old mtime
{ {
let project = Project::test(fs.clone(), ["/file.rs".as_ref()], cx).await; let project = Project::test(fs.clone(), [path!("/file.rs").as_ref()], cx).await;
let (workspace, cx) = let (workspace, cx) =
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx)); cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
@ -1893,7 +1894,7 @@ mod tests {
let item_id = 9345 as ItemId; let item_id = 9345 as ItemId;
let old_mtime = MTime::from_seconds_and_nanos(0, 50); let old_mtime = MTime::from_seconds_and_nanos(0, 50);
let serialized_editor = SerializedEditor { let serialized_editor = SerializedEditor {
abs_path: Some(PathBuf::from("/file.rs")), abs_path: Some(PathBuf::from(path!("/file.rs"))),
contents: Some("fn main() {}".to_string()), contents: Some("fn main() {}".to_string()),
language: Some("Rust".to_string()), language: Some("Rust".to_string()),
mtime: Some(old_mtime), mtime: Some(old_mtime),

View file

@ -1,12 +1,15 @@
pub mod editor_lsp_test_context; pub mod editor_lsp_test_context;
pub mod editor_test_context; pub mod editor_test_context;
use std::sync::LazyLock;
use crate::{ use crate::{
display_map::{DisplayMap, DisplaySnapshot, ToDisplayPoint}, display_map::{DisplayMap, DisplaySnapshot, ToDisplayPoint},
DisplayPoint, Editor, EditorMode, FoldPlaceholder, MultiBuffer, DisplayPoint, Editor, EditorMode, FoldPlaceholder, MultiBuffer,
}; };
use gpui::{ use gpui::{
AppContext as _, Context, Entity, Font, FontFeatures, FontStyle, FontWeight, Pixels, Window, font, AppContext as _, Context, Entity, Font, FontFeatures, FontStyle, FontWeight, Pixels,
Window,
}; };
use project::Project; use project::Project;
use util::test::{marked_text_offsets, marked_text_ranges}; use util::test::{marked_text_offsets, marked_text_ranges};
@ -19,6 +22,22 @@ fn init_logger() {
} }
} }
pub fn test_font() -> Font {
static TEST_FONT: LazyLock<Font> = LazyLock::new(|| {
#[cfg(not(target_os = "windows"))]
{
font("Helvetica")
}
#[cfg(target_os = "windows")]
{
font("Courier New")
}
});
TEST_FONT.clone()
}
// Returns a snapshot from text containing '|' character markers with the markers removed, and DisplayPoints for each one. // Returns a snapshot from text containing '|' character markers with the markers removed, and DisplayPoints for each one.
pub fn marked_display_snapshot( pub fn marked_display_snapshot(
text: &str, text: &str,

View file

@ -455,7 +455,12 @@ async fn test_extension_store(cx: &mut TestAppContext) {
}); });
} }
// todo(windows)
// Disable this test on Windows for now. Because this test hangs at
// `let fake_server = fake_servers.next().await.unwrap();`.
// Reenable this test when we figure out why.
#[gpui::test] #[gpui::test]
#[cfg_attr(target_os = "windows", ignore)]
async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) { async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) {
init_test(cx); init_test(cx);
cx.executor().allow_parking(); cx.executor().allow_parking();
@ -634,6 +639,8 @@ async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) {
.await .await
.unwrap(); .unwrap();
// todo(windows)
// This test hangs here on Windows.
let fake_server = fake_servers.next().await.unwrap(); let fake_server = fake_servers.next().await.unwrap();
let expected_server_path = let expected_server_path =
extensions_dir.join(format!("work/{test_extension_id}/gleam-v1.2.3/gleam")); extensions_dir.join(format!("work/{test_extension_id}/gleam-v1.2.3/gleam"));

View file

@ -6,6 +6,7 @@ use gpui::{Entity, TestAppContext, VisualTestContext};
use menu::{Confirm, SelectNext, SelectPrev}; use menu::{Confirm, SelectNext, SelectPrev};
use project::{RemoveOptions, FS_WATCH_LATENCY}; use project::{RemoveOptions, FS_WATCH_LATENCY};
use serde_json::json; use serde_json::json;
use util::path;
use workspace::{AppState, ToggleFileFinder, Workspace}; use workspace::{AppState, ToggleFileFinder, Workspace};
#[ctor::ctor] #[ctor::ctor]
@ -90,7 +91,7 @@ async fn test_absolute_paths(cx: &mut TestAppContext) {
.fs .fs
.as_fake() .as_fake()
.insert_tree( .insert_tree(
"/root", path!("/root"),
json!({ json!({
"a": { "a": {
"file1.txt": "", "file1.txt": "",
@ -102,16 +103,16 @@ async fn test_absolute_paths(cx: &mut TestAppContext) {
) )
.await; .await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; let project = Project::test(app_state.fs.clone(), [path!("/root").as_ref()], cx).await;
let (picker, workspace, cx) = build_find_picker(project, cx); let (picker, workspace, cx) = build_find_picker(project, cx);
let matching_abs_path = "/root/a/b/file2.txt"; let matching_abs_path = path!("/root/a/b/file2.txt").to_string();
picker picker
.update_in(cx, |picker, window, cx| { .update_in(cx, |picker, window, cx| {
picker picker
.delegate .delegate
.update_matches(matching_abs_path.to_string(), window, cx) .update_matches(matching_abs_path, window, cx)
}) })
.await; .await;
picker.update(cx, |picker, _| { picker.update(cx, |picker, _| {
@ -128,12 +129,12 @@ async fn test_absolute_paths(cx: &mut TestAppContext) {
assert_eq!(active_editor.read(cx).title(cx), "file2.txt"); assert_eq!(active_editor.read(cx).title(cx), "file2.txt");
}); });
let mismatching_abs_path = "/root/a/b/file1.txt"; let mismatching_abs_path = path!("/root/a/b/file1.txt").to_string();
picker picker
.update_in(cx, |picker, window, cx| { .update_in(cx, |picker, window, cx| {
picker picker
.delegate .delegate
.update_matches(mismatching_abs_path.to_string(), window, cx) .update_matches(mismatching_abs_path, window, cx)
}) })
.await; .await;
picker.update(cx, |picker, _| { picker.update(cx, |picker, _| {
@ -518,7 +519,7 @@ async fn test_path_distance_ordering(cx: &mut TestAppContext) {
.fs .fs
.as_fake() .as_fake()
.insert_tree( .insert_tree(
"/root", path!("/root"),
json!({ json!({
"dir1": { "a.txt": "" }, "dir1": { "a.txt": "" },
"dir2": { "dir2": {
@ -529,7 +530,7 @@ async fn test_path_distance_ordering(cx: &mut TestAppContext) {
) )
.await; .await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; let project = Project::test(app_state.fs.clone(), [path!("/root").as_ref()], cx).await;
let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx)); let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
let worktree_id = cx.read(|cx| { let worktree_id = cx.read(|cx| {
@ -606,7 +607,7 @@ async fn test_query_history(cx: &mut gpui::TestAppContext) {
.fs .fs
.as_fake() .as_fake()
.insert_tree( .insert_tree(
"/src", path!("/src"),
json!({ json!({
"test": { "test": {
"first.rs": "// First Rust file", "first.rs": "// First Rust file",
@ -617,7 +618,7 @@ async fn test_query_history(cx: &mut gpui::TestAppContext) {
) )
.await; .await;
let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).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)); let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
let worktree_id = cx.read(|cx| { let worktree_id = cx.read(|cx| {
let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>(); let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
@ -648,7 +649,7 @@ async fn test_query_history(cx: &mut gpui::TestAppContext) {
worktree_id, worktree_id,
path: Arc::from(Path::new("test/first.rs")), path: Arc::from(Path::new("test/first.rs")),
}, },
Some(PathBuf::from("/src/test/first.rs")) Some(PathBuf::from(path!("/src/test/first.rs")))
)], )],
"Should show 1st opened item in the history when opening the 2nd item" "Should show 1st opened item in the history when opening the 2nd item"
); );
@ -663,14 +664,14 @@ async fn test_query_history(cx: &mut gpui::TestAppContext) {
worktree_id, worktree_id,
path: Arc::from(Path::new("test/second.rs")), path: Arc::from(Path::new("test/second.rs")),
}, },
Some(PathBuf::from("/src/test/second.rs")) Some(PathBuf::from(path!("/src/test/second.rs")))
), ),
FoundPath::new( FoundPath::new(
ProjectPath { ProjectPath {
worktree_id, worktree_id,
path: Arc::from(Path::new("test/first.rs")), path: Arc::from(Path::new("test/first.rs")),
}, },
Some(PathBuf::from("/src/test/first.rs")) Some(PathBuf::from(path!("/src/test/first.rs")))
), ),
], ],
"Should show 1st and 2nd opened items in the history when opening the 3rd item. \ "Should show 1st and 2nd opened items in the history when opening the 3rd item. \
@ -687,21 +688,21 @@ async fn test_query_history(cx: &mut gpui::TestAppContext) {
worktree_id, worktree_id,
path: Arc::from(Path::new("test/third.rs")), path: Arc::from(Path::new("test/third.rs")),
}, },
Some(PathBuf::from("/src/test/third.rs")) Some(PathBuf::from(path!("/src/test/third.rs")))
), ),
FoundPath::new( FoundPath::new(
ProjectPath { ProjectPath {
worktree_id, worktree_id,
path: Arc::from(Path::new("test/second.rs")), path: Arc::from(Path::new("test/second.rs")),
}, },
Some(PathBuf::from("/src/test/second.rs")) Some(PathBuf::from(path!("/src/test/second.rs")))
), ),
FoundPath::new( FoundPath::new(
ProjectPath { ProjectPath {
worktree_id, worktree_id,
path: Arc::from(Path::new("test/first.rs")), path: Arc::from(Path::new("test/first.rs")),
}, },
Some(PathBuf::from("/src/test/first.rs")) Some(PathBuf::from(path!("/src/test/first.rs")))
), ),
], ],
"Should show 1st, 2nd and 3rd opened items in the history when opening the 2nd item again. \ "Should show 1st, 2nd and 3rd opened items in the history when opening the 2nd item again. \
@ -718,21 +719,21 @@ async fn test_query_history(cx: &mut gpui::TestAppContext) {
worktree_id, worktree_id,
path: Arc::from(Path::new("test/second.rs")), path: Arc::from(Path::new("test/second.rs")),
}, },
Some(PathBuf::from("/src/test/second.rs")) Some(PathBuf::from(path!("/src/test/second.rs")))
), ),
FoundPath::new( FoundPath::new(
ProjectPath { ProjectPath {
worktree_id, worktree_id,
path: Arc::from(Path::new("test/third.rs")), path: Arc::from(Path::new("test/third.rs")),
}, },
Some(PathBuf::from("/src/test/third.rs")) Some(PathBuf::from(path!("/src/test/third.rs")))
), ),
FoundPath::new( FoundPath::new(
ProjectPath { ProjectPath {
worktree_id, worktree_id,
path: Arc::from(Path::new("test/first.rs")), path: Arc::from(Path::new("test/first.rs")),
}, },
Some(PathBuf::from("/src/test/first.rs")) Some(PathBuf::from(path!("/src/test/first.rs")))
), ),
], ],
"Should show 1st, 2nd and 3rd opened items in the history when opening the 3rd item again. \ "Should show 1st, 2nd and 3rd opened items in the history when opening the 3rd item again. \
@ -748,7 +749,7 @@ async fn test_external_files_history(cx: &mut gpui::TestAppContext) {
.fs .fs
.as_fake() .as_fake()
.insert_tree( .insert_tree(
"/src", path!("/src"),
json!({ json!({
"test": { "test": {
"first.rs": "// First Rust file", "first.rs": "// First Rust file",
@ -762,7 +763,7 @@ async fn test_external_files_history(cx: &mut gpui::TestAppContext) {
.fs .fs
.as_fake() .as_fake()
.insert_tree( .insert_tree(
"/external-src", path!("/external-src"),
json!({ json!({
"test": { "test": {
"third.rs": "// Third Rust file", "third.rs": "// Third Rust file",
@ -772,10 +773,10 @@ async fn test_external_files_history(cx: &mut gpui::TestAppContext) {
) )
.await; .await;
let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await; let project = Project::test(app_state.fs.clone(), [path!("/src").as_ref()], cx).await;
cx.update(|cx| { cx.update(|cx| {
project.update(cx, |project, cx| { project.update(cx, |project, cx| {
project.find_or_create_worktree("/external-src", false, cx) project.find_or_create_worktree(path!("/external-src"), false, cx)
}) })
}) })
.detach(); .detach();
@ -791,7 +792,7 @@ async fn test_external_files_history(cx: &mut gpui::TestAppContext) {
workspace workspace
.update_in(cx, |workspace, window, cx| { .update_in(cx, |workspace, window, cx| {
workspace.open_abs_path( workspace.open_abs_path(
PathBuf::from("/external-src/test/third.rs"), PathBuf::from(path!("/external-src/test/third.rs")),
false, false,
window, window,
cx, cx,
@ -827,7 +828,7 @@ async fn test_external_files_history(cx: &mut gpui::TestAppContext) {
worktree_id: external_worktree_id, worktree_id: external_worktree_id,
path: Arc::from(Path::new("")), path: Arc::from(Path::new("")),
}, },
Some(PathBuf::from("/external-src/test/third.rs")) Some(PathBuf::from(path!("/external-src/test/third.rs")))
)], )],
"Should show external file with its full path in the history after it was open" "Should show external file with its full path in the history after it was open"
); );
@ -842,14 +843,14 @@ async fn test_external_files_history(cx: &mut gpui::TestAppContext) {
worktree_id, worktree_id,
path: Arc::from(Path::new("test/second.rs")), path: Arc::from(Path::new("test/second.rs")),
}, },
Some(PathBuf::from("/src/test/second.rs")) Some(PathBuf::from(path!("/src/test/second.rs")))
), ),
FoundPath::new( FoundPath::new(
ProjectPath { ProjectPath {
worktree_id: external_worktree_id, worktree_id: external_worktree_id,
path: Arc::from(Path::new("")), path: Arc::from(Path::new("")),
}, },
Some(PathBuf::from("/external-src/test/third.rs")) Some(PathBuf::from(path!("/external-src/test/third.rs")))
), ),
], ],
"Should keep external file with history updates", "Should keep external file with history updates",
@ -864,7 +865,7 @@ async fn test_toggle_panel_new_selections(cx: &mut gpui::TestAppContext) {
.fs .fs
.as_fake() .as_fake()
.insert_tree( .insert_tree(
"/src", path!("/src"),
json!({ json!({
"test": { "test": {
"first.rs": "// First Rust file", "first.rs": "// First Rust file",
@ -875,7 +876,7 @@ async fn test_toggle_panel_new_selections(cx: &mut gpui::TestAppContext) {
) )
.await; .await;
let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).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)); let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
// generate some history to select from // generate some history to select from
@ -919,7 +920,7 @@ async fn test_search_preserves_history_items(cx: &mut gpui::TestAppContext) {
.fs .fs
.as_fake() .as_fake()
.insert_tree( .insert_tree(
"/src", path!("/src"),
json!({ json!({
"test": { "test": {
"first.rs": "// First Rust file", "first.rs": "// First Rust file",
@ -931,7 +932,7 @@ async fn test_search_preserves_history_items(cx: &mut gpui::TestAppContext) {
) )
.await; .await;
let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).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)); let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
let worktree_id = cx.read(|cx| { let worktree_id = cx.read(|cx| {
let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>(); let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
@ -964,7 +965,7 @@ async fn test_search_preserves_history_items(cx: &mut gpui::TestAppContext) {
worktree_id, worktree_id,
path: Arc::from(Path::new("test/first.rs")), path: Arc::from(Path::new("test/first.rs")),
}, },
Some(PathBuf::from("/src/test/first.rs")) Some(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.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(), Path::new("test/fourth.rs"));
@ -1007,7 +1008,7 @@ async fn test_search_preserves_history_items(cx: &mut gpui::TestAppContext) {
worktree_id, worktree_id,
path: Arc::from(Path::new("test/first.rs")), path: Arc::from(Path::new("test/first.rs")),
}, },
Some(PathBuf::from("/src/test/first.rs")) Some(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.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(), Path::new("test/fourth.rs"));
@ -1022,7 +1023,7 @@ async fn test_search_sorts_history_items(cx: &mut gpui::TestAppContext) {
.fs .fs
.as_fake() .as_fake()
.insert_tree( .insert_tree(
"/root", path!("/root"),
json!({ json!({
"test": { "test": {
"1_qw": "// First file that matches the query", "1_qw": "// First file that matches the query",
@ -1037,7 +1038,7 @@ async fn test_search_sorts_history_items(cx: &mut gpui::TestAppContext) {
) )
.await; .await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; let project = Project::test(app_state.fs.clone(), [path!("/root").as_ref()], cx).await;
let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx)); let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
// generate some history to select from // generate some history to select from
open_close_queried_buffer("1", 1, "1_qw", &workspace, cx).await; open_close_queried_buffer("1", 1, "1_qw", &workspace, cx).await;
@ -1079,7 +1080,7 @@ async fn test_select_current_open_file_when_no_history(cx: &mut gpui::TestAppCon
.fs .fs
.as_fake() .as_fake()
.insert_tree( .insert_tree(
"/root", path!("/root"),
json!({ json!({
"test": { "test": {
"1_qw": "", "1_qw": "",
@ -1088,7 +1089,7 @@ async fn test_select_current_open_file_when_no_history(cx: &mut gpui::TestAppCon
) )
.await; .await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; let project = Project::test(app_state.fs.clone(), [path!("/root").as_ref()], cx).await;
let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx)); let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
// Open new buffer // Open new buffer
open_queried_buffer("1", 1, "1_qw", &workspace, cx).await; open_queried_buffer("1", 1, "1_qw", &workspace, cx).await;
@ -1109,7 +1110,7 @@ async fn test_keep_opened_file_on_top_of_search_results_and_select_next_one(
.fs .fs
.as_fake() .as_fake()
.insert_tree( .insert_tree(
"/src", path!("/src"),
json!({ json!({
"test": { "test": {
"bar.rs": "// Bar file", "bar.rs": "// Bar file",
@ -1122,7 +1123,7 @@ async fn test_keep_opened_file_on_top_of_search_results_and_select_next_one(
) )
.await; .await;
let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).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)); let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
open_close_queried_buffer("bar", 1, "bar.rs", &workspace, cx).await; open_close_queried_buffer("bar", 1, "bar.rs", &workspace, cx).await;
@ -1202,7 +1203,7 @@ async fn test_non_separate_history_items(cx: &mut TestAppContext) {
.fs .fs
.as_fake() .as_fake()
.insert_tree( .insert_tree(
"/src", path!("/src"),
json!({ json!({
"test": { "test": {
"bar.rs": "// Bar file", "bar.rs": "// Bar file",
@ -1215,7 +1216,7 @@ async fn test_non_separate_history_items(cx: &mut TestAppContext) {
) )
.await; .await;
let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).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)); let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
open_close_queried_buffer("bar", 1, "bar.rs", &workspace, cx).await; open_close_queried_buffer("bar", 1, "bar.rs", &workspace, cx).await;
@ -1296,7 +1297,7 @@ async fn test_history_items_shown_in_order_of_open(cx: &mut TestAppContext) {
.fs .fs
.as_fake() .as_fake()
.insert_tree( .insert_tree(
"/test", path!("/test"),
json!({ json!({
"test": { "test": {
"1.txt": "// One", "1.txt": "// One",
@ -1307,7 +1308,7 @@ async fn test_history_items_shown_in_order_of_open(cx: &mut TestAppContext) {
) )
.await; .await;
let project = Project::test(app_state.fs.clone(), ["/test".as_ref()], cx).await; let project = Project::test(app_state.fs.clone(), [path!("/test").as_ref()], cx).await;
let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx)); let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
open_queried_buffer("1", 1, "1.txt", &workspace, cx).await; open_queried_buffer("1", 1, "1.txt", &workspace, cx).await;
@ -1354,7 +1355,7 @@ async fn test_selected_history_item_stays_selected_on_worktree_updated(cx: &mut
.fs .fs
.as_fake() .as_fake()
.insert_tree( .insert_tree(
"/test", path!("/test"),
json!({ json!({
"test": { "test": {
"1.txt": "// One", "1.txt": "// One",
@ -1365,7 +1366,7 @@ async fn test_selected_history_item_stays_selected_on_worktree_updated(cx: &mut
) )
.await; .await;
let project = Project::test(app_state.fs.clone(), ["/test".as_ref()], cx).await; let project = Project::test(app_state.fs.clone(), [path!("/test").as_ref()], cx).await;
let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx)); let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
open_close_queried_buffer("1", 1, "1.txt", &workspace, cx).await; open_close_queried_buffer("1", 1, "1.txt", &workspace, cx).await;
@ -1384,7 +1385,11 @@ async fn test_selected_history_item_stays_selected_on_worktree_updated(cx: &mut
// Add more files to the worktree to trigger update matches // Add more files to the worktree to trigger update matches
for i in 0..5 { for i in 0..5 {
let filename = format!("/test/{}.txt", 4 + i); let filename = if cfg!(windows) {
format!("C:/test/{}.txt", 4 + i)
} else {
format!("/test/{}.txt", 4 + i)
};
app_state app_state
.fs .fs
.create_file(Path::new(&filename), Default::default()) .create_file(Path::new(&filename), Default::default())
@ -1410,7 +1415,7 @@ async fn test_history_items_vs_very_good_external_match(cx: &mut gpui::TestAppCo
.fs .fs
.as_fake() .as_fake()
.insert_tree( .insert_tree(
"/src", path!("/src"),
json!({ json!({
"collab_ui": { "collab_ui": {
"first.rs": "// First Rust file", "first.rs": "// First Rust file",
@ -1422,7 +1427,7 @@ async fn test_history_items_vs_very_good_external_match(cx: &mut gpui::TestAppCo
) )
.await; .await;
let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).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)); let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
// generate some history to select from // generate some history to select from
open_close_queried_buffer("fir", 1, "first.rs", &workspace, cx).await; open_close_queried_buffer("fir", 1, "first.rs", &workspace, cx).await;
@ -1456,7 +1461,7 @@ async fn test_nonexistent_history_items_not_shown(cx: &mut gpui::TestAppContext)
.fs .fs
.as_fake() .as_fake()
.insert_tree( .insert_tree(
"/src", path!("/src"),
json!({ json!({
"test": { "test": {
"first.rs": "// First Rust file", "first.rs": "// First Rust file",
@ -1467,7 +1472,7 @@ async fn test_nonexistent_history_items_not_shown(cx: &mut gpui::TestAppContext)
) )
.await; .await;
let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).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)); // generate some history to select from let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx)); // generate some history to select from
open_close_queried_buffer("fir", 1, "first.rs", &workspace, cx).await; open_close_queried_buffer("fir", 1, "first.rs", &workspace, cx).await;
open_close_queried_buffer("non", 1, "nonexistent.rs", &workspace, cx).await; open_close_queried_buffer("non", 1, "nonexistent.rs", &workspace, cx).await;
@ -1476,7 +1481,7 @@ async fn test_nonexistent_history_items_not_shown(cx: &mut gpui::TestAppContext)
app_state app_state
.fs .fs
.remove_file( .remove_file(
Path::new("/src/test/nonexistent.rs"), Path::new(path!("/src/test/nonexistent.rs")),
RemoveOptions::default(), RemoveOptions::default(),
) )
.await .await
@ -1742,14 +1747,14 @@ async fn test_keeps_file_finder_open_after_modifier_keys_release(cx: &mut gpui::
.fs .fs
.as_fake() .as_fake()
.insert_tree( .insert_tree(
"/test", path!("/test"),
json!({ json!({
"1.txt": "// One", "1.txt": "// One",
}), }),
) )
.await; .await;
let project = Project::test(app_state.fs.clone(), ["/test".as_ref()], cx).await; let project = Project::test(app_state.fs.clone(), [path!("/test").as_ref()], cx).await;
let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx)); let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
open_queried_buffer("1", 1, "1.txt", &workspace, cx).await; open_queried_buffer("1", 1, "1.txt", &workspace, cx).await;
@ -1809,7 +1814,7 @@ async fn test_switches_between_release_norelease_modes_on_forward_nav(
.fs .fs
.as_fake() .as_fake()
.insert_tree( .insert_tree(
"/test", path!("/test"),
json!({ json!({
"1.txt": "// One", "1.txt": "// One",
"2.txt": "// Two", "2.txt": "// Two",
@ -1817,7 +1822,7 @@ async fn test_switches_between_release_norelease_modes_on_forward_nav(
) )
.await; .await;
let project = Project::test(app_state.fs.clone(), ["/test".as_ref()], cx).await; let project = Project::test(app_state.fs.clone(), [path!("/test").as_ref()], cx).await;
let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx)); let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
open_queried_buffer("1", 1, "1.txt", &workspace, cx).await; open_queried_buffer("1", 1, "1.txt", &workspace, cx).await;
@ -1864,7 +1869,7 @@ async fn test_switches_between_release_norelease_modes_on_backward_nav(
.fs .fs
.as_fake() .as_fake()
.insert_tree( .insert_tree(
"/test", path!("/test"),
json!({ json!({
"1.txt": "// One", "1.txt": "// One",
"2.txt": "// Two", "2.txt": "// Two",
@ -1873,7 +1878,7 @@ async fn test_switches_between_release_norelease_modes_on_backward_nav(
) )
.await; .await;
let project = Project::test(app_state.fs.clone(), ["/test".as_ref()], cx).await; let project = Project::test(app_state.fs.clone(), [path!("/test").as_ref()], cx).await;
let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx)); let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
open_queried_buffer("1", 1, "1.txt", &workspace, cx).await; open_queried_buffer("1", 1, "1.txt", &workspace, cx).await;
@ -1921,14 +1926,14 @@ async fn test_extending_modifiers_does_not_confirm_selection(cx: &mut gpui::Test
.fs .fs
.as_fake() .as_fake()
.insert_tree( .insert_tree(
"/test", path!("/test"),
json!({ json!({
"1.txt": "// One", "1.txt": "// One",
}), }),
) )
.await; .await;
let project = Project::test(app_state.fs.clone(), ["/test".as_ref()], cx).await; let project = Project::test(app_state.fs.clone(), [path!("/test").as_ref()], cx).await;
let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx)); let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
open_queried_buffer("1", 1, "1.txt", &workspace, cx).await; open_queried_buffer("1", 1, "1.txt", &workspace, cx).await;

View file

@ -9,6 +9,8 @@ const BASE_DISTANCE_PENALTY: f64 = 0.6;
const ADDITIONAL_DISTANCE_PENALTY: f64 = 0.05; const ADDITIONAL_DISTANCE_PENALTY: f64 = 0.05;
const MIN_DISTANCE_PENALTY: f64 = 0.2; const MIN_DISTANCE_PENALTY: f64 = 0.2;
// TODO:
// Use `Path` instead of `&str` for paths.
pub struct Matcher<'a> { pub struct Matcher<'a> {
query: &'a [char], query: &'a [char],
lowercase_query: &'a [char], lowercase_query: &'a [char],
@ -173,6 +175,8 @@ impl<'a> Matcher<'a> {
path_idx: usize, path_idx: usize,
cur_score: f64, cur_score: f64,
) -> f64 { ) -> f64 {
use std::path::MAIN_SEPARATOR;
if query_idx == self.query.len() { if query_idx == self.query.len() {
return 1.0; return 1.0;
} }
@ -196,13 +200,19 @@ impl<'a> Matcher<'a> {
} else { } else {
path_cased[j - prefix.len()] path_cased[j - prefix.len()]
}; };
let is_path_sep = path_char == '/' || path_char == '\\'; let is_path_sep = path_char == MAIN_SEPARATOR;
if query_idx == 0 && is_path_sep { if query_idx == 0 && is_path_sep {
last_slash = j; last_slash = j;
} }
if query_char == path_char || (is_path_sep && query_char == '_' || query_char == '\\') { #[cfg(not(target_os = "windows"))]
let need_to_score =
query_char == path_char || (is_path_sep && query_char == '_' || query_char == '\\');
// `query_char == '\\'` breaks `test_match_path_entries` on Windows, `\` is only used as a path separator on Windows.
#[cfg(target_os = "windows")]
let need_to_score = query_char == path_char || (is_path_sep && query_char == '_');
if need_to_score {
let curr = if j < prefix.len() { let curr = if j < prefix.len() {
prefix[j] prefix[j]
} else { } else {
@ -217,7 +227,7 @@ impl<'a> Matcher<'a> {
path[j - 1 - prefix.len()] path[j - 1 - prefix.len()]
}; };
if last == '/' { if last == MAIN_SEPARATOR {
char_score = 0.9; char_score = 0.9;
} else if (last == '-' || last == '_' || last == ' ' || last.is_numeric()) } else if (last == '-' || last == '_' || last == ' ' || last.is_numeric())
|| (last.is_lowercase() && curr.is_uppercase()) || (last.is_lowercase() && curr.is_uppercase())
@ -238,7 +248,7 @@ impl<'a> Matcher<'a> {
// Apply a severe penalty if the case doesn't match. // Apply a severe penalty if the case doesn't match.
// This will make the exact matches have higher score than the case-insensitive and the // This will make the exact matches have higher score than the case-insensitive and the
// path insensitive matches. // path insensitive matches.
if (self.smart_case || curr == '/') && self.query[query_idx] != curr { if (self.smart_case || curr == MAIN_SEPARATOR) && self.query[query_idx] != curr {
char_score *= 0.001; char_score *= 0.001;
} }
@ -322,6 +332,7 @@ mod tests {
assert_eq!(matcher.last_positions, vec![0, 3, 4, 8]); assert_eq!(matcher.last_positions, vec![0, 3, 4, 8]);
} }
#[cfg(not(target_os = "windows"))]
#[test] #[test]
fn test_match_path_entries() { fn test_match_path_entries() {
let paths = vec![ let paths = vec![
@ -363,6 +374,54 @@ mod tests {
); );
} }
/// todo(windows)
/// Now, on Windows, users can only use the backslash as a path separator.
/// I do want to support both the backslash and the forward slash as path separators on Windows.
#[cfg(target_os = "windows")]
#[test]
fn test_match_path_entries() {
let paths = vec![
"",
"a",
"ab",
"abC",
"abcd",
"alphabravocharlie",
"AlphaBravoCharlie",
"thisisatestdir",
"\\\\\\\\\\ThisIsATestDir",
"\\this\\is\\a\\test\\dir",
"\\test\\tiatd",
];
assert_eq!(
match_single_path_query("abc", false, &paths),
vec![
("abC", vec![0, 1, 2]),
("abcd", vec![0, 1, 2]),
("AlphaBravoCharlie", vec![0, 5, 10]),
("alphabravocharlie", vec![4, 5, 10]),
]
);
assert_eq!(
match_single_path_query("t\\i\\a\\t\\d", false, &paths),
vec![(
"\\this\\is\\a\\test\\dir",
vec![1, 5, 6, 8, 9, 10, 11, 15, 16]
),]
);
assert_eq!(
match_single_path_query("tiatd", false, &paths),
vec![
("\\test\\tiatd", vec![6, 7, 8, 9, 10]),
("\\this\\is\\a\\test\\dir", vec![1, 6, 9, 11, 16]),
("\\\\\\\\\\ThisIsATestDir", vec![5, 9, 11, 12, 16]),
("thisisatestdir", vec![0, 2, 6, 7, 11]),
]
);
}
#[test] #[test]
fn test_lowercase_longer_than_uppercase() { fn test_lowercase_longer_than_uppercase() {
// This character has more chars in lower-case than in upper-case. // This character has more chars in lower-case than in upper-case.

View file

@ -353,7 +353,7 @@ mod tests {
let want_json = let want_json =
std::fs::read_to_string(&path).unwrap_or_else(|_| { std::fs::read_to_string(&path).unwrap_or_else(|_| {
panic!("could not read golden test data file at {:?}. Did you run the test with UPDATE_GOLDEN=true before?", path); panic!("could not read golden test data file at {:?}. Did you run the test with UPDATE_GOLDEN=true before?", path);
}); }).replace("\r\n", "\n");
pretty_assertions::assert_eq!(have_json, want_json, "wrong blame entries"); pretty_assertions::assert_eq!(have_json, want_json, "wrong blame entries");
} }

View file

@ -428,17 +428,24 @@ impl DirectWriteState {
target_font.fallbacks.as_ref(), target_font.fallbacks.as_ref(),
) )
.unwrap_or_else(|| { .unwrap_or_else(|| {
let family = self.system_ui_font_name.clone(); #[cfg(any(test, feature = "test-support"))]
log::error!("{} not found, use {} instead.", target_font.family, family); {
self.get_font_id_from_font_collection( panic!("ERROR: {} font not found!", target_font.family);
family.as_ref(), }
target_font.weight, #[cfg(not(any(test, feature = "test-support")))]
target_font.style, {
&target_font.features, let family = self.system_ui_font_name.clone();
target_font.fallbacks.as_ref(), log::error!("{} not found, use {} instead.", target_font.family, family);
true, self.get_font_id_from_font_collection(
) family.as_ref(),
.unwrap() target_font.weight,
target_font.style,
&target_font.features,
target_font.fallbacks.as_ref(),
true,
)
.unwrap()
}
}) })
} }
} }

View file

@ -756,21 +756,20 @@ fn should_auto_hide_scrollbars() -> Result<bool> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::{ClipboardItem, Platform, WindowsPlatform}; use crate::{read_from_clipboard, write_to_clipboard, ClipboardItem};
#[test] #[test]
fn test_clipboard() { fn test_clipboard() {
let platform = WindowsPlatform::new(); let item = ClipboardItem::new_string("你好,我是张小白".to_string());
let item = ClipboardItem::new_string("你好".to_string()); write_to_clipboard(item.clone());
platform.write_to_clipboard(item.clone()); assert_eq!(read_from_clipboard(), Some(item));
assert_eq!(platform.read_from_clipboard(), Some(item));
let item = ClipboardItem::new_string("12345".to_string()); let item = ClipboardItem::new_string("12345".to_string());
platform.write_to_clipboard(item.clone()); write_to_clipboard(item.clone());
assert_eq!(platform.read_from_clipboard(), Some(item)); assert_eq!(read_from_clipboard(), Some(item));
let item = ClipboardItem::new_string_with_json_metadata("abcdef".to_string(), vec![3, 4]); let item = ClipboardItem::new_string_with_json_metadata("abcdef".to_string(), vec![3, 4]);
platform.write_to_clipboard(item.clone()); write_to_clipboard(item.clone());
assert_eq!(platform.read_from_clipboard(), Some(item)); assert_eq!(read_from_clipboard(), Some(item));
} }
} }

View file

@ -14,9 +14,9 @@ proc-macro = true
doctest = true doctest = true
[dependencies] [dependencies]
proc-macro2 = "1.0.66" proc-macro2.workspace = true
quote = "1.0.9" quote.workspace = true
syn = { version = "1.0.72", features = ["full", "extra-traits"] } syn.workspace = true
[dev-dependencies] [dev-dependencies]
gpui.workspace = true gpui.workspace = true

View file

@ -11,6 +11,7 @@ use lsp_log::LogKind;
use project::{FakeFs, Project}; use project::{FakeFs, Project};
use serde_json::json; use serde_json::json;
use settings::SettingsStore; use settings::SettingsStore;
use util::path;
#[gpui::test] #[gpui::test]
async fn test_lsp_logs(cx: &mut TestAppContext) { async fn test_lsp_logs(cx: &mut TestAppContext) {
@ -22,7 +23,7 @@ async fn test_lsp_logs(cx: &mut TestAppContext) {
let fs = FakeFs::new(cx.background_executor.clone()); let fs = FakeFs::new(cx.background_executor.clone());
fs.insert_tree( fs.insert_tree(
"/the-root", path!("/the-root"),
json!({ json!({
"test.rs": "", "test.rs": "",
"package.json": "", "package.json": "",
@ -30,7 +31,7 @@ async fn test_lsp_logs(cx: &mut TestAppContext) {
) )
.await; .await;
let project = Project::test(fs.clone(), ["/the-root".as_ref()], cx).await; let project = Project::test(fs.clone(), [path!("/the-root").as_ref()], cx).await;
let language_registry = project.read_with(cx, |project, _| project.languages().clone()); let language_registry = project.read_with(cx, |project, _| project.languages().clone());
language_registry.add(Arc::new(Language::new( language_registry.add(Arc::new(Language::new(
@ -57,7 +58,7 @@ async fn test_lsp_logs(cx: &mut TestAppContext) {
let _rust_buffer = project let _rust_buffer = project
.update(cx, |project, cx| { .update(cx, |project, cx| {
project.open_local_buffer_with_lsp("/the-root/test.rs", cx) project.open_local_buffer_with_lsp(path!("/the-root/test.rs"), cx)
}) })
.await .await
.unwrap(); .unwrap();

View file

@ -818,11 +818,12 @@ mod tests {
use lsp::CompletionItemLabelDetails; use lsp::CompletionItemLabelDetails;
use settings::SettingsStore; use settings::SettingsStore;
use theme::SyntaxTheme; use theme::SyntaxTheme;
use util::path;
#[gpui::test] #[gpui::test]
async fn test_process_rust_diagnostics() { async fn test_process_rust_diagnostics() {
let mut params = lsp::PublishDiagnosticsParams { let mut params = lsp::PublishDiagnosticsParams {
uri: lsp::Url::from_file_path("/a").unwrap(), uri: lsp::Url::from_file_path(path!("/a")).unwrap(),
version: None, version: None,
diagnostics: vec![ diagnostics: vec![
// no newlines // no newlines

View file

@ -946,7 +946,7 @@ mod tests {
.await { .await {
Ok(path) => panic!("Expected to fail for prettier in package.json but not in node_modules found, but got path {path:?}"), Ok(path) => panic!("Expected to fail for prettier in package.json but not in node_modules found, but got path {path:?}"),
Err(e) => { Err(e) => {
let message = e.to_string(); let message = e.to_string().replace("\\\\", "/");
assert!(message.contains("/root/work/full-stack-foundations/exercises/03.loading/01.problem.loader"), "Error message should mention which project had prettier defined"); assert!(message.contains("/root/work/full-stack-foundations/exercises/03.loading/01.problem.loader"), "Error message should mention which project had prettier defined");
assert!(message.contains("/root/work/full-stack-foundations"), "Error message should mention potential candidates without prettier node_modules contents"); assert!(message.contains("/root/work/full-stack-foundations"), "Error message should mention potential candidates without prettier node_modules contents");
}, },

File diff suppressed because it is too large Load diff

View file

@ -18,7 +18,7 @@ use task::{
ResolvedTask, TaskContext, TaskId, TaskTemplate, TaskTemplates, TaskVariables, VariableName, ResolvedTask, TaskContext, TaskId, TaskTemplate, TaskTemplates, TaskVariables, VariableName,
}; };
use text::{Point, ToPoint}; use text::{Point, ToPoint};
use util::{post_inc, NumericPrefixWithSuffix, ResultExt as _}; use util::{paths::PathExt as _, post_inc, NumericPrefixWithSuffix, ResultExt as _};
use worktree::WorktreeId; use worktree::WorktreeId;
use crate::worktree_store::WorktreeStore; use crate::worktree_store::WorktreeStore;
@ -470,7 +470,7 @@ impl ContextProvider for BasicContextProvider {
let current_file = buffer let current_file = buffer
.file() .file()
.and_then(|file| file.as_local()) .and_then(|file| file.as_local())
.map(|file| file.abs_path(cx).to_string_lossy().to_string()); .map(|file| file.abs_path(cx).to_sanitized_string());
let Point { row, column } = location.range.start.to_point(&buffer_snapshot); let Point { row, column } = location.range.start.to_point(&buffer_snapshot);
let row = row + 1; let row = row + 1;
let column = column + 1; let column = column + 1;
@ -502,14 +502,14 @@ impl ContextProvider for BasicContextProvider {
if let Some(Some(worktree_path)) = worktree_root_dir { if let Some(Some(worktree_path)) = worktree_root_dir {
task_variables.insert( task_variables.insert(
VariableName::WorktreeRoot, VariableName::WorktreeRoot,
worktree_path.to_string_lossy().to_string(), worktree_path.to_sanitized_string(),
); );
if let Some(full_path) = current_file.as_ref() { if let Some(full_path) = current_file.as_ref() {
let relative_path = pathdiff::diff_paths(full_path, worktree_path); let relative_path = pathdiff::diff_paths(full_path, worktree_path);
if let Some(relative_path) = relative_path { if let Some(relative_path) = relative_path {
task_variables.insert( task_variables.insert(
VariableName::RelativeFile, VariableName::RelativeFile,
relative_path.to_string_lossy().into_owned(), relative_path.to_sanitized_string(),
); );
} }
} }

View file

@ -1106,8 +1106,13 @@ impl ProjectPanel {
let worktree_id = edit_state.worktree_id; let worktree_id = edit_state.worktree_id;
let is_new_entry = edit_state.is_new_entry(); let is_new_entry = edit_state.is_new_entry();
let filename = self.filename_editor.read(cx).text(cx); let filename = self.filename_editor.read(cx).text(cx);
edit_state.is_dir = edit_state.is_dir #[cfg(not(target_os = "windows"))]
|| (edit_state.is_new_entry() && filename.ends_with(std::path::MAIN_SEPARATOR)); let filename_indicates_dir = filename.ends_with("/");
// On Windows, path separator could be either `/` or `\`.
#[cfg(target_os = "windows")]
let filename_indicates_dir = filename.ends_with("/") || filename.ends_with("\\");
edit_state.is_dir =
edit_state.is_dir || (edit_state.is_new_entry() && filename_indicates_dir);
let is_dir = edit_state.is_dir; let is_dir = edit_state.is_dir;
let worktree = self.project.read(cx).worktree_for_id(worktree_id, cx)?; let worktree = self.project.read(cx).worktree_for_id(worktree_id, cx)?;
let entry = worktree.read(cx).entry_for_id(edit_state.entry_id)?.clone(); let entry = worktree.read(cx).entry_for_id(edit_state.entry_id)?.clone();
@ -4793,6 +4798,7 @@ mod tests {
use serde_json::json; use serde_json::json;
use settings::SettingsStore; use settings::SettingsStore;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use util::{path, separator};
use workspace::{ use workspace::{
item::{Item, ProjectItem}, item::{Item, ProjectItem},
register_project_item, AppState, register_project_item, AppState,
@ -4894,7 +4900,7 @@ mod tests {
let fs = FakeFs::new(cx.executor().clone()); let fs = FakeFs::new(cx.executor().clone());
fs.insert_tree( fs.insert_tree(
"/src", path!("/src"),
json!({ json!({
"test": { "test": {
"first.rs": "// First Rust file", "first.rs": "// First Rust file",
@ -4905,7 +4911,7 @@ mod tests {
) )
.await; .await;
let project = Project::test(fs.clone(), ["/src".as_ref()], cx).await; let project = Project::test(fs.clone(), [path!("/src").as_ref()], cx).await;
let workspace = let workspace =
cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx)); cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
let cx = &mut VisualTestContext::from_window(*workspace, cx); let cx = &mut VisualTestContext::from_window(*workspace, cx);
@ -5066,7 +5072,7 @@ mod tests {
let fs = FakeFs::new(cx.executor().clone()); let fs = FakeFs::new(cx.executor().clone());
fs.insert_tree( fs.insert_tree(
"/root1", path!("/root1"),
json!({ json!({
"dir_1": { "dir_1": {
"nested_dir_1": { "nested_dir_1": {
@ -5088,7 +5094,7 @@ mod tests {
) )
.await; .await;
fs.insert_tree( fs.insert_tree(
"/root2", path!("/root2"),
json!({ json!({
"dir_2": { "dir_2": {
"file_1.java": "// File contents", "file_1.java": "// File contents",
@ -5097,7 +5103,12 @@ mod tests {
) )
.await; .await;
let project = Project::test(fs.clone(), ["/root1".as_ref(), "/root2".as_ref()], cx).await; let project = Project::test(
fs.clone(),
[path!("/root1").as_ref(), path!("/root2").as_ref()],
cx,
)
.await;
let workspace = let workspace =
cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx)); cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
let cx = &mut VisualTestContext::from_window(*workspace, cx); let cx = &mut VisualTestContext::from_window(*workspace, cx);
@ -5115,10 +5126,10 @@ mod tests {
assert_eq!( assert_eq!(
visible_entries_as_strings(&panel, 0..10, cx), visible_entries_as_strings(&panel, 0..10, cx),
&[ &[
"v root1", separator!("v root1"),
" > dir_1/nested_dir_1/nested_dir_2/nested_dir_3", separator!(" > dir_1/nested_dir_1/nested_dir_2/nested_dir_3"),
"v root2", separator!("v root2"),
" > dir_2", separator!(" > dir_2"),
] ]
); );
@ -5130,14 +5141,14 @@ mod tests {
assert_eq!( assert_eq!(
visible_entries_as_strings(&panel, 0..10, cx), visible_entries_as_strings(&panel, 0..10, cx),
&[ &[
"v root1", separator!("v root1"),
" v dir_1/nested_dir_1/nested_dir_2/nested_dir_3 <== selected", separator!(" v dir_1/nested_dir_1/nested_dir_2/nested_dir_3 <== selected"),
" > nested_dir_4/nested_dir_5", separator!(" > nested_dir_4/nested_dir_5"),
" file_a.java", separator!(" file_a.java"),
" file_b.java", separator!(" file_b.java"),
" file_c.java", separator!(" file_c.java"),
"v root2", separator!("v root2"),
" > dir_2", separator!(" > dir_2"),
] ]
); );
@ -5149,31 +5160,31 @@ mod tests {
assert_eq!( assert_eq!(
visible_entries_as_strings(&panel, 0..10, cx), visible_entries_as_strings(&panel, 0..10, cx),
&[ &[
"v root1", separator!("v root1"),
" v dir_1/nested_dir_1/nested_dir_2/nested_dir_3", separator!(" v dir_1/nested_dir_1/nested_dir_2/nested_dir_3"),
" v nested_dir_4/nested_dir_5 <== selected", separator!(" v nested_dir_4/nested_dir_5 <== selected"),
" file_d.java", separator!(" file_d.java"),
" file_a.java", separator!(" file_a.java"),
" file_b.java", separator!(" file_b.java"),
" file_c.java", separator!(" file_c.java"),
"v root2", separator!("v root2"),
" > dir_2", separator!(" > dir_2"),
] ]
); );
toggle_expand_dir(&panel, "root2/dir_2", cx); toggle_expand_dir(&panel, "root2/dir_2", cx);
assert_eq!( assert_eq!(
visible_entries_as_strings(&panel, 0..10, cx), visible_entries_as_strings(&panel, 0..10, cx),
&[ &[
"v root1", separator!("v root1"),
" v dir_1/nested_dir_1/nested_dir_2/nested_dir_3", separator!(" v dir_1/nested_dir_1/nested_dir_2/nested_dir_3"),
" v nested_dir_4/nested_dir_5", separator!(" v nested_dir_4/nested_dir_5"),
" file_d.java", separator!(" file_d.java"),
" file_a.java", separator!(" file_a.java"),
" file_b.java", separator!(" file_b.java"),
" file_c.java", separator!(" file_c.java"),
"v root2", separator!("v root2"),
" v dir_2 <== selected", separator!(" v dir_2 <== selected"),
" file_1.java", separator!(" file_1.java"),
] ]
); );
} }
@ -5682,7 +5693,7 @@ mod tests {
let fs = FakeFs::new(cx.executor().clone()); let fs = FakeFs::new(cx.executor().clone());
fs.insert_tree( fs.insert_tree(
"/root1", path!("/root1"),
json!({ json!({
".dockerignore": "", ".dockerignore": "",
".git": { ".git": {
@ -5692,7 +5703,7 @@ mod tests {
) )
.await; .await;
let project = Project::test(fs.clone(), ["/root1".as_ref()], cx).await; let project = Project::test(fs.clone(), [path!("/root1").as_ref()], cx).await;
let workspace = let workspace =
cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx)); cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
let cx = &mut VisualTestContext::from_window(*workspace, cx); let cx = &mut VisualTestContext::from_window(*workspace, cx);
@ -5727,9 +5738,10 @@ mod tests {
); );
let confirm = panel.update_in(cx, |panel, window, cx| { let confirm = panel.update_in(cx, |panel, window, cx| {
// If we want to create a subdirectory, there should be no prefix slash.
panel panel
.filename_editor .filename_editor
.update(cx, |editor, cx| editor.set_text("/new_dir/", window, cx)); .update(cx, |editor, cx| editor.set_text("new_dir/", window, cx));
panel.confirm_edit(window, cx).unwrap() panel.confirm_edit(window, cx).unwrap()
}); });
@ -5738,14 +5750,14 @@ mod tests {
&[ &[
"v root1", "v root1",
" > .git", " > .git",
" [PROCESSING: '/new_dir/'] <== selected", " [PROCESSING: 'new_dir/'] <== selected",
" .dockerignore", " .dockerignore",
] ]
); );
confirm.await.unwrap(); confirm.await.unwrap();
assert_eq!( assert_eq!(
visible_entries_as_strings(&panel, 0..13, cx), visible_entries_as_strings(&panel, 0..10, cx),
&[ &[
"v root1", "v root1",
" > .git", " > .git",
@ -5753,6 +5765,54 @@ mod tests {
" .dockerignore", " .dockerignore",
] ]
); );
// Test filename with whitespace
select_path(&panel, "root1", cx);
panel.update_in(cx, |panel, window, cx| panel.new_file(&NewFile, window, cx));
let confirm = panel.update_in(cx, |panel, window, cx| {
// If we want to create a subdirectory, there should be no prefix slash.
panel
.filename_editor
.update(cx, |editor, cx| editor.set_text("new dir 2/", window, cx));
panel.confirm_edit(window, cx).unwrap()
});
confirm.await.unwrap();
assert_eq!(
visible_entries_as_strings(&panel, 0..10, cx),
&[
"v root1",
" > .git",
" v new dir 2 <== selected",
" v new_dir",
" .dockerignore",
]
);
// Test filename ends with "\"
#[cfg(target_os = "windows")]
{
select_path(&panel, "root1", cx);
panel.update_in(cx, |panel, window, cx| panel.new_file(&NewFile, window, cx));
let confirm = panel.update_in(cx, |panel, window, cx| {
// If we want to create a subdirectory, there should be no prefix slash.
panel
.filename_editor
.update(cx, |editor, cx| editor.set_text("new_dir_3\\", window, cx));
panel.confirm_edit(window, cx).unwrap()
});
confirm.await.unwrap();
assert_eq!(
visible_entries_as_strings(&panel, 0..10, cx),
&[
"v root1",
" > .git",
" v new dir 2",
" v new_dir",
" v new_dir_3 <== selected",
" .dockerignore",
]
);
}
} }
#[gpui::test] #[gpui::test]
@ -6409,7 +6469,7 @@ mod tests {
let fs = FakeFs::new(cx.executor().clone()); let fs = FakeFs::new(cx.executor().clone());
fs.insert_tree( fs.insert_tree(
"/src", path!("/src"),
json!({ json!({
"test": { "test": {
"first.rs": "// First Rust file", "first.rs": "// First Rust file",
@ -6420,7 +6480,7 @@ mod tests {
) )
.await; .await;
let project = Project::test(fs.clone(), ["/src".as_ref()], cx).await; let project = Project::test(fs.clone(), [path!("/src").as_ref()], cx).await;
let workspace = let workspace =
cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx)); cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
let cx = &mut VisualTestContext::from_window(*workspace, cx); let cx = &mut VisualTestContext::from_window(*workspace, cx);
@ -8545,7 +8605,7 @@ mod tests {
let fs = FakeFs::new(cx.executor().clone()); let fs = FakeFs::new(cx.executor().clone());
fs.insert_tree( fs.insert_tree(
"/root", path!("/root"),
json!({ json!({
".gitignore": "**/ignored_dir\n**/ignored_nested", ".gitignore": "**/ignored_dir\n**/ignored_nested",
"dir1": { "dir1": {
@ -8573,7 +8633,7 @@ mod tests {
) )
.await; .await;
let project = Project::test(fs.clone(), ["/root".as_ref()], cx).await; let project = Project::test(fs.clone(), [path!("/root").as_ref()], cx).await;
let workspace = let workspace =
cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx)); cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
let cx = &mut VisualTestContext::from_window(*workspace, cx); let cx = &mut VisualTestContext::from_window(*workspace, cx);
@ -8602,12 +8662,12 @@ mod tests {
assert_eq!( assert_eq!(
visible_entries_as_strings(&panel, 0..20, cx), visible_entries_as_strings(&panel, 0..20, cx),
&[ &[
"v root", separator!("v root"),
" v dir1 <== selected", separator!(" v dir1 <== selected"),
" > empty1/empty2/empty3", separator!(" > empty1/empty2/empty3"),
" > ignored_dir", separator!(" > ignored_dir"),
" > subdir1", separator!(" > subdir1"),
" .gitignore", separator!(" .gitignore"),
], ],
"Should show first level with auto-folded dirs and ignored dir visible" "Should show first level with auto-folded dirs and ignored dir visible"
); );
@ -8624,18 +8684,18 @@ mod tests {
assert_eq!( assert_eq!(
visible_entries_as_strings(&panel, 0..20, cx), visible_entries_as_strings(&panel, 0..20, cx),
&[ &[
"v root", separator!("v root"),
" v dir1 <== selected", separator!(" v dir1 <== selected"),
" v empty1", separator!(" v empty1"),
" v empty2", separator!(" v empty2"),
" v empty3", separator!(" v empty3"),
" file.txt", separator!(" file.txt"),
" > ignored_dir", separator!(" > ignored_dir"),
" v subdir1", separator!(" v subdir1"),
" > ignored_nested", separator!(" > ignored_nested"),
" file1.txt", separator!(" file1.txt"),
" file2.txt", separator!(" file2.txt"),
" .gitignore", separator!(" .gitignore"),
], ],
"After expand_all with auto-fold: should not expand ignored_dir, should expand folded dirs, and should not expand ignored_nested" "After expand_all with auto-fold: should not expand ignored_dir, should expand folded dirs, and should not expand ignored_nested"
); );
@ -8660,12 +8720,12 @@ mod tests {
assert_eq!( assert_eq!(
visible_entries_as_strings(&panel, 0..20, cx), visible_entries_as_strings(&panel, 0..20, cx),
&[ &[
"v root", separator!("v root"),
" v dir1 <== selected", separator!(" v dir1 <== selected"),
" > empty1", separator!(" > empty1"),
" > ignored_dir", separator!(" > ignored_dir"),
" > subdir1", separator!(" > subdir1"),
" .gitignore", separator!(" .gitignore"),
], ],
"With auto-fold disabled: should show all directories separately" "With auto-fold disabled: should show all directories separately"
); );
@ -8682,18 +8742,18 @@ mod tests {
assert_eq!( assert_eq!(
visible_entries_as_strings(&panel, 0..20, cx), visible_entries_as_strings(&panel, 0..20, cx),
&[ &[
"v root", separator!("v root"),
" v dir1 <== selected", separator!(" v dir1 <== selected"),
" v empty1", separator!(" v empty1"),
" v empty2", separator!(" v empty2"),
" v empty3", separator!(" v empty3"),
" file.txt", separator!(" file.txt"),
" > ignored_dir", separator!(" > ignored_dir"),
" v subdir1", separator!(" v subdir1"),
" > ignored_nested", separator!(" > ignored_nested"),
" file1.txt", separator!(" file1.txt"),
" file2.txt", separator!(" file2.txt"),
" .gitignore", separator!(" .gitignore"),
], ],
"After expand_all without auto-fold: should expand all dirs normally, \ "After expand_all without auto-fold: should expand all dirs normally, \
expand ignored_dir itself but not its subdirs, and not expand ignored_nested" expand ignored_dir itself but not its subdirs, and not expand ignored_nested"
@ -8712,20 +8772,20 @@ mod tests {
assert_eq!( assert_eq!(
visible_entries_as_strings(&panel, 0..20, cx), visible_entries_as_strings(&panel, 0..20, cx),
&[ &[
"v root", separator!("v root"),
" v dir1 <== selected", separator!(" v dir1 <== selected"),
" v empty1", separator!(" v empty1"),
" v empty2", separator!(" v empty2"),
" v empty3", separator!(" v empty3"),
" file.txt", separator!(" file.txt"),
" v ignored_dir", separator!(" v ignored_dir"),
" v subdir", separator!(" v subdir"),
" deep_file.txt", separator!(" deep_file.txt"),
" v subdir1", separator!(" v subdir1"),
" > ignored_nested", separator!(" > ignored_nested"),
" file1.txt", separator!(" file1.txt"),
" file2.txt", separator!(" file2.txt"),
" .gitignore", separator!(" .gitignore"),
], ],
"After expand_all on ignored_dir: should expand all contents of the ignored directory" "After expand_all on ignored_dir: should expand all contents of the ignored directory"
); );
@ -8737,7 +8797,7 @@ mod tests {
let fs = FakeFs::new(cx.executor().clone()); let fs = FakeFs::new(cx.executor().clone());
fs.insert_tree( fs.insert_tree(
"/root", path!("/root"),
json!({ json!({
"dir1": { "dir1": {
"subdir1": { "subdir1": {
@ -8759,7 +8819,7 @@ mod tests {
) )
.await; .await;
let project = Project::test(fs.clone(), ["/root".as_ref()], cx).await; let project = Project::test(fs.clone(), [path!("/root").as_ref()], cx).await;
let workspace = let workspace =
cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx)); cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
let cx = &mut VisualTestContext::from_window(*workspace, cx); let cx = &mut VisualTestContext::from_window(*workspace, cx);
@ -8776,15 +8836,15 @@ mod tests {
assert_eq!( assert_eq!(
visible_entries_as_strings(&panel, 0..20, cx), visible_entries_as_strings(&panel, 0..20, cx),
&[ &[
"v root", separator!("v root"),
" v dir1", separator!(" v dir1"),
" v subdir1", separator!(" v subdir1"),
" v nested1", separator!(" v nested1"),
" file1.txt", separator!(" file1.txt"),
" file2.txt", separator!(" file2.txt"),
" v subdir2 <== selected", separator!(" v subdir2 <== selected"),
" file4.txt", separator!(" file4.txt"),
" > dir2", separator!(" > dir2"),
], ],
"Initial state with everything expanded" "Initial state with everything expanded"
); );
@ -8826,13 +8886,13 @@ mod tests {
assert_eq!( assert_eq!(
visible_entries_as_strings(&panel, 0..20, cx), visible_entries_as_strings(&panel, 0..20, cx),
&[ &[
"v root", separator!("v root"),
" v dir1", separator!(" v dir1"),
" v subdir1/nested1 <== selected", separator!(" v subdir1/nested1 <== selected"),
" file1.txt", separator!(" file1.txt"),
" file2.txt", separator!(" file2.txt"),
" > subdir2", separator!(" > subdir2"),
" > dir2/single_file", separator!(" > dir2/single_file"),
], ],
"Initial state with some dirs expanded" "Initial state with some dirs expanded"
); );
@ -8849,11 +8909,11 @@ mod tests {
assert_eq!( assert_eq!(
visible_entries_as_strings(&panel, 0..20, cx), visible_entries_as_strings(&panel, 0..20, cx),
&[ &[
"v root", separator!("v root"),
" v dir1 <== selected", separator!(" v dir1 <== selected"),
" > subdir1/nested1", separator!(" > subdir1/nested1"),
" > subdir2", separator!(" > subdir2"),
" > dir2/single_file", separator!(" > dir2/single_file"),
], ],
"Subdirs should be collapsed and folded with auto-fold enabled" "Subdirs should be collapsed and folded with auto-fold enabled"
); );
@ -8881,14 +8941,14 @@ mod tests {
assert_eq!( assert_eq!(
visible_entries_as_strings(&panel, 0..20, cx), visible_entries_as_strings(&panel, 0..20, cx),
&[ &[
"v root", separator!("v root"),
" v dir1", separator!(" v dir1"),
" v subdir1", separator!(" v subdir1"),
" v nested1 <== selected", separator!(" v nested1 <== selected"),
" file1.txt", separator!(" file1.txt"),
" file2.txt", separator!(" file2.txt"),
" > subdir2", separator!(" > subdir2"),
" > dir2", separator!(" > dir2"),
], ],
"Initial state with some dirs expanded and auto-fold disabled" "Initial state with some dirs expanded and auto-fold disabled"
); );
@ -8905,11 +8965,11 @@ mod tests {
assert_eq!( assert_eq!(
visible_entries_as_strings(&panel, 0..20, cx), visible_entries_as_strings(&panel, 0..20, cx),
&[ &[
"v root", separator!("v root"),
" v dir1 <== selected", separator!(" v dir1 <== selected"),
" > subdir1", separator!(" > subdir1"),
" > subdir2", separator!(" > subdir2"),
" > dir2", separator!(" > dir2"),
], ],
"Subdirs should be collapsed but not folded with auto-fold disabled" "Subdirs should be collapsed but not folded with auto-fold disabled"
); );

View file

@ -272,15 +272,17 @@ mod tests {
use serde_json::json; use serde_json::json;
use settings::SettingsStore; use settings::SettingsStore;
use std::{path::Path, sync::Arc}; use std::{path::Path, sync::Arc};
use util::path;
#[gpui::test] #[gpui::test]
async fn test_project_symbols(cx: &mut TestAppContext) { async fn test_project_symbols(cx: &mut TestAppContext) {
init_test(cx); init_test(cx);
let fs = FakeFs::new(cx.executor()); let fs = FakeFs::new(cx.executor());
fs.insert_tree("/dir", json!({ "test.rs": "" })).await; fs.insert_tree(path!("/dir"), json!({ "test.rs": "" }))
.await;
let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
let language_registry = project.read_with(cx, |project, _| project.languages().clone()); let language_registry = project.read_with(cx, |project, _| project.languages().clone());
language_registry.add(Arc::new(Language::new( language_registry.add(Arc::new(Language::new(
@ -299,7 +301,7 @@ mod tests {
let _buffer = project let _buffer = project
.update(cx, |project, cx| { .update(cx, |project, cx| {
project.open_local_buffer_with_lsp("/dir/test.rs", cx) project.open_local_buffer_with_lsp(path!("/dir/test.rs"), cx)
}) })
.await .await
.unwrap(); .unwrap();
@ -307,9 +309,9 @@ mod tests {
// Set up fake language server to return fuzzy matches against // Set up fake language server to return fuzzy matches against
// a fixed set of symbol names. // a fixed set of symbol names.
let fake_symbols = [ let fake_symbols = [
symbol("one", "/external"), symbol("one", path!("/external")),
symbol("ton", "/dir/test.rs"), symbol("ton", path!("/dir/test.rs")),
symbol("uno", "/dir/test.rs"), symbol("uno", path!("/dir/test.rs")),
]; ];
let fake_server = fake_servers.next().await.unwrap(); let fake_server = fake_servers.next().await.unwrap();
fake_server.handle_request::<lsp::WorkspaceSymbolRequest, _, _>( fake_server.handle_request::<lsp::WorkspaceSymbolRequest, _, _>(

View file

@ -595,6 +595,7 @@ mod tests {
use project::{project_settings::ProjectSettings, Project}; use project::{project_settings::ProjectSettings, Project};
use serde_json::json; use serde_json::json;
use settings::SettingsStore; use settings::SettingsStore;
use util::path;
use workspace::{open_paths, AppState}; use workspace::{open_paths, AppState};
use super::*; use super::*;
@ -615,7 +616,7 @@ mod tests {
.fs .fs
.as_fake() .as_fake()
.insert_tree( .insert_tree(
"/dir", path!("/dir"),
json!({ json!({
"main.ts": "a" "main.ts": "a"
}), }),
@ -623,7 +624,7 @@ mod tests {
.await; .await;
cx.update(|cx| { cx.update(|cx| {
open_paths( open_paths(
&[PathBuf::from("/dir/main.ts")], &[PathBuf::from(path!("/dir/main.ts"))],
app_state, app_state,
workspace::OpenOptions::default(), workspace::OpenOptions::default(),
cx, cx,

View file

@ -14,6 +14,6 @@ proc-macro = true
doctest = false doctest = false
[dependencies] [dependencies]
syn = "1.0.72" proc-macro2.workspace = true
quote = "1.0.9" quote.workspace = true
proc-macro2 = "1.0.66" syn.workspace = true

View file

@ -1,3 +1,6 @@
/// todo(windows)
/// The tests in this file assume that server_cx is running on Windows too.
/// We neead to find a way to test Windows-Non-Windows interactions.
use crate::headless_project::HeadlessProject; use crate::headless_project::HeadlessProject;
use client::{Client, UserStore}; use client::{Client, UserStore};
use clock::FakeSystemClock; use clock::FakeSystemClock;
@ -24,12 +27,13 @@ use std::{
path::{Path, PathBuf}, path::{Path, PathBuf},
sync::Arc, sync::Arc,
}; };
use util::{path, separator};
#[gpui::test] #[gpui::test]
async fn test_basic_remote_editing(cx: &mut TestAppContext, server_cx: &mut TestAppContext) { async fn test_basic_remote_editing(cx: &mut TestAppContext, server_cx: &mut TestAppContext) {
let fs = FakeFs::new(server_cx.executor()); let fs = FakeFs::new(server_cx.executor());
fs.insert_tree( fs.insert_tree(
"/code", path!("/code"),
json!({ json!({
"project1": { "project1": {
".git": {}, ".git": {},
@ -45,14 +49,14 @@ async fn test_basic_remote_editing(cx: &mut TestAppContext, server_cx: &mut Test
) )
.await; .await;
fs.set_index_for_repo( fs.set_index_for_repo(
Path::new("/code/project1/.git"), Path::new(path!("/code/project1/.git")),
&[("src/lib.rs".into(), "fn one() -> usize { 0 }".into())], &[("src/lib.rs".into(), "fn one() -> usize { 0 }".into())],
); );
let (project, _headless) = init_test(&fs, cx, server_cx).await; let (project, _headless) = init_test(&fs, cx, server_cx).await;
let (worktree, _) = project let (worktree, _) = project
.update(cx, |project, cx| { .update(cx, |project, cx| {
project.find_or_create_worktree("/code/project1", true, cx) project.find_or_create_worktree(path!("/code/project1"), true, cx)
}) })
.await .await
.unwrap(); .unwrap();
@ -113,7 +117,7 @@ async fn test_basic_remote_editing(cx: &mut TestAppContext, server_cx: &mut Test
// A new file is created in the remote filesystem. The user // A new file is created in the remote filesystem. The user
// sees the new file. // sees the new file.
fs.save( fs.save(
"/code/project1/src/main.rs".as_ref(), path!("/code/project1/src/main.rs").as_ref(),
&"fn main() {}".into(), &"fn main() {}".into(),
Default::default(), Default::default(),
) )
@ -134,8 +138,8 @@ async fn test_basic_remote_editing(cx: &mut TestAppContext, server_cx: &mut Test
// A file that is currently open in a buffer is renamed. // A file that is currently open in a buffer is renamed.
fs.rename( fs.rename(
"/code/project1/src/lib.rs".as_ref(), path!("/code/project1/src/lib.rs").as_ref(),
"/code/project1/src/lib2.rs".as_ref(), path!("/code/project1/src/lib2.rs").as_ref(),
Default::default(), Default::default(),
) )
.await .await
@ -146,7 +150,7 @@ async fn test_basic_remote_editing(cx: &mut TestAppContext, server_cx: &mut Test
}); });
fs.set_index_for_repo( fs.set_index_for_repo(
Path::new("/code/project1/.git"), Path::new(path!("/code/project1/.git")),
&[("src/lib2.rs".into(), "fn one() -> usize { 100 }".into())], &[("src/lib2.rs".into(), "fn one() -> usize { 100 }".into())],
); );
cx.executor().run_until_parked(); cx.executor().run_until_parked();
@ -162,7 +166,7 @@ async fn test_basic_remote_editing(cx: &mut TestAppContext, server_cx: &mut Test
async fn test_remote_project_search(cx: &mut TestAppContext, server_cx: &mut TestAppContext) { async fn test_remote_project_search(cx: &mut TestAppContext, server_cx: &mut TestAppContext) {
let fs = FakeFs::new(server_cx.executor()); let fs = FakeFs::new(server_cx.executor());
fs.insert_tree( fs.insert_tree(
"/code", path!("/code"),
json!({ json!({
"project1": { "project1": {
".git": {}, ".git": {},
@ -179,7 +183,7 @@ async fn test_remote_project_search(cx: &mut TestAppContext, server_cx: &mut Tes
project project
.update(cx, |project, cx| { .update(cx, |project, cx| {
project.find_or_create_worktree("/code/project1", true, cx) project.find_or_create_worktree(path!("/code/project1"), true, cx)
}) })
.await .await
.unwrap(); .unwrap();
@ -210,7 +214,7 @@ async fn test_remote_project_search(cx: &mut TestAppContext, server_cx: &mut Tes
buffer.update(&mut cx, |buffer, cx| { buffer.update(&mut cx, |buffer, cx| {
assert_eq!( assert_eq!(
buffer.file().unwrap().full_path(cx).to_string_lossy(), buffer.file().unwrap().full_path(cx).to_string_lossy(),
"project1/README.md" separator!("project1/README.md")
) )
}); });
@ -368,7 +372,7 @@ async fn test_remote_settings(cx: &mut TestAppContext, server_cx: &mut TestAppCo
async fn test_remote_lsp(cx: &mut TestAppContext, server_cx: &mut TestAppContext) { async fn test_remote_lsp(cx: &mut TestAppContext, server_cx: &mut TestAppContext) {
let fs = FakeFs::new(server_cx.executor()); let fs = FakeFs::new(server_cx.executor());
fs.insert_tree( fs.insert_tree(
"/code", path!("/code"),
json!({ json!({
"project1": { "project1": {
".git": {}, ".git": {},
@ -384,7 +388,7 @@ async fn test_remote_lsp(cx: &mut TestAppContext, server_cx: &mut TestAppContext
let (project, headless) = init_test(&fs, cx, server_cx).await; let (project, headless) = init_test(&fs, cx, server_cx).await;
fs.insert_tree( fs.insert_tree(
"/code/project1/.zed", path!("/code/project1/.zed"),
json!({ json!({
"settings.json": r#" "settings.json": r#"
{ {
@ -431,7 +435,7 @@ async fn test_remote_lsp(cx: &mut TestAppContext, server_cx: &mut TestAppContext
let worktree_id = project let worktree_id = project
.update(cx, |project, cx| { .update(cx, |project, cx| {
project.find_or_create_worktree("/code/project1", true, cx) project.find_or_create_worktree(path!("/code/project1"), true, cx)
}) })
.await .await
.unwrap() .unwrap()
@ -512,7 +516,7 @@ async fn test_remote_lsp(cx: &mut TestAppContext, server_cx: &mut TestAppContext
Ok(Some(lsp::WorkspaceEdit { Ok(Some(lsp::WorkspaceEdit {
changes: Some( changes: Some(
[( [(
lsp::Url::from_file_path("/code/project1/src/lib.rs").unwrap(), lsp::Url::from_file_path(path!("/code/project1/src/lib.rs")).unwrap(),
vec![lsp::TextEdit::new( vec![lsp::TextEdit::new(
lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 6)), lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 6)),
"two".to_string(), "two".to_string(),
@ -545,7 +549,7 @@ async fn test_remote_cancel_language_server_work(
) { ) {
let fs = FakeFs::new(server_cx.executor()); let fs = FakeFs::new(server_cx.executor());
fs.insert_tree( fs.insert_tree(
"/code", path!("/code"),
json!({ json!({
"project1": { "project1": {
".git": {}, ".git": {},
@ -561,7 +565,7 @@ async fn test_remote_cancel_language_server_work(
let (project, headless) = init_test(&fs, cx, server_cx).await; let (project, headless) = init_test(&fs, cx, server_cx).await;
fs.insert_tree( fs.insert_tree(
"/code/project1/.zed", path!("/code/project1/.zed"),
json!({ json!({
"settings.json": r#" "settings.json": r#"
{ {
@ -608,7 +612,7 @@ async fn test_remote_cancel_language_server_work(
let worktree_id = project let worktree_id = project
.update(cx, |project, cx| { .update(cx, |project, cx| {
project.find_or_create_worktree("/code/project1", true, cx) project.find_or_create_worktree(path!("/code/project1"), true, cx)
}) })
.await .await
.unwrap() .unwrap()
@ -708,7 +712,7 @@ async fn test_remote_cancel_language_server_work(
async fn test_remote_reload(cx: &mut TestAppContext, server_cx: &mut TestAppContext) { async fn test_remote_reload(cx: &mut TestAppContext, server_cx: &mut TestAppContext) {
let fs = FakeFs::new(server_cx.executor()); let fs = FakeFs::new(server_cx.executor());
fs.insert_tree( fs.insert_tree(
"/code", path!("/code"),
json!({ json!({
"project1": { "project1": {
".git": {}, ".git": {},
@ -724,7 +728,7 @@ async fn test_remote_reload(cx: &mut TestAppContext, server_cx: &mut TestAppCont
let (project, _headless) = init_test(&fs, cx, server_cx).await; let (project, _headless) = init_test(&fs, cx, server_cx).await;
let (worktree, _) = project let (worktree, _) = project
.update(cx, |project, cx| { .update(cx, |project, cx| {
project.find_or_create_worktree("/code/project1", true, cx) project.find_or_create_worktree(path!("/code/project1"), true, cx)
}) })
.await .await
.unwrap(); .unwrap();
@ -739,7 +743,7 @@ async fn test_remote_reload(cx: &mut TestAppContext, server_cx: &mut TestAppCont
.unwrap(); .unwrap();
fs.save( fs.save(
&PathBuf::from("/code/project1/src/lib.rs"), &PathBuf::from(path!("/code/project1/src/lib.rs")),
&("bangles".to_string().into()), &("bangles".to_string().into()),
LineEnding::Unix, LineEnding::Unix,
) )
@ -754,7 +758,7 @@ async fn test_remote_reload(cx: &mut TestAppContext, server_cx: &mut TestAppCont
}); });
fs.save( fs.save(
&PathBuf::from("/code/project1/src/lib.rs"), &PathBuf::from(path!("/code/project1/src/lib.rs")),
&("bloop".to_string().into()), &("bloop".to_string().into()),
LineEnding::Unix, LineEnding::Unix,
) )
@ -786,7 +790,7 @@ async fn test_remote_resolve_path_in_buffer(
) { ) {
let fs = FakeFs::new(server_cx.executor()); let fs = FakeFs::new(server_cx.executor());
fs.insert_tree( fs.insert_tree(
"/code", path!("/code"),
json!({ json!({
"project1": { "project1": {
".git": {}, ".git": {},
@ -802,7 +806,7 @@ async fn test_remote_resolve_path_in_buffer(
let (project, _headless) = init_test(&fs, cx, server_cx).await; let (project, _headless) = init_test(&fs, cx, server_cx).await;
let (worktree, _) = project let (worktree, _) = project
.update(cx, |project, cx| { .update(cx, |project, cx| {
project.find_or_create_worktree("/code/project1", true, cx) project.find_or_create_worktree(path!("/code/project1"), true, cx)
}) })
.await .await
.unwrap(); .unwrap();
@ -818,14 +822,14 @@ async fn test_remote_resolve_path_in_buffer(
let path = project let path = project
.update(cx, |project, cx| { .update(cx, |project, cx| {
project.resolve_path_in_buffer("/code/project1/README.md", &buffer, cx) project.resolve_path_in_buffer(path!("/code/project1/README.md"), &buffer, cx)
}) })
.await .await
.unwrap(); .unwrap();
assert!(path.is_file()); assert!(path.is_file());
assert_eq!( assert_eq!(
path.abs_path().unwrap().to_string_lossy(), path.abs_path().unwrap().to_string_lossy(),
"/code/project1/README.md" path!("/code/project1/README.md")
); );
let path = project let path = project
@ -1013,7 +1017,7 @@ async fn test_adding_then_removing_then_adding_worktrees(
async fn test_open_server_settings(cx: &mut TestAppContext, server_cx: &mut TestAppContext) { async fn test_open_server_settings(cx: &mut TestAppContext, server_cx: &mut TestAppContext) {
let fs = FakeFs::new(server_cx.executor()); let fs = FakeFs::new(server_cx.executor());
fs.insert_tree( fs.insert_tree(
"/code", path!("/code"),
json!({ json!({
"project1": { "project1": {
".git": {}, ".git": {},
@ -1035,7 +1039,9 @@ async fn test_open_server_settings(cx: &mut TestAppContext, server_cx: &mut Test
cx.update(|cx| { cx.update(|cx| {
assert_eq!( assert_eq!(
buffer.read(cx).text(), buffer.read(cx).text(),
initial_server_settings_content().to_string() initial_server_settings_content()
.to_string()
.replace("\r\n", "\n")
) )
}) })
} }
@ -1044,7 +1050,7 @@ async fn test_open_server_settings(cx: &mut TestAppContext, server_cx: &mut Test
async fn test_reconnect(cx: &mut TestAppContext, server_cx: &mut TestAppContext) { async fn test_reconnect(cx: &mut TestAppContext, server_cx: &mut TestAppContext) {
let fs = FakeFs::new(server_cx.executor()); let fs = FakeFs::new(server_cx.executor());
fs.insert_tree( fs.insert_tree(
"/code", path!("/code"),
json!({ json!({
"project1": { "project1": {
".git": {}, ".git": {},
@ -1061,7 +1067,7 @@ async fn test_reconnect(cx: &mut TestAppContext, server_cx: &mut TestAppContext)
let (worktree, _) = project let (worktree, _) = project
.update(cx, |project, cx| { .update(cx, |project, cx| {
project.find_or_create_worktree("/code/project1", true, cx) project.find_or_create_worktree(path!("/code/project1"), true, cx)
}) })
.await .await
.unwrap(); .unwrap();
@ -1091,7 +1097,9 @@ async fn test_reconnect(cx: &mut TestAppContext, server_cx: &mut TestAppContext)
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
fs.load("/code/project1/src/lib.rs".as_ref()).await.unwrap(), fs.load(path!("/code/project1/src/lib.rs").as_ref())
.await
.unwrap(),
"fn one() -> usize { 100 }" "fn one() -> usize { 100 }"
); );
} }

View file

@ -2188,6 +2188,7 @@ pub mod tests {
use project::FakeFs; use project::FakeFs;
use serde_json::json; use serde_json::json;
use settings::SettingsStore; use settings::SettingsStore;
use util::path;
use workspace::DeploySearch; use workspace::DeploySearch;
#[gpui::test] #[gpui::test]
@ -3313,13 +3314,13 @@ pub mod tests {
let fs = FakeFs::new(cx.background_executor.clone()); let fs = FakeFs::new(cx.background_executor.clone());
fs.insert_tree( fs.insert_tree(
"/dir", path!("/dir"),
json!({ json!({
"one.rs": "const ONE: usize = 1;", "one.rs": "const ONE: usize = 1;",
}), }),
) )
.await; .await;
let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
let worktree_id = project.update(cx, |this, cx| { let worktree_id = project.update(cx, |this, cx| {
this.worktrees(cx).next().unwrap().read(cx).id() this.worktrees(cx).next().unwrap().read(cx).id()
}); });
@ -3537,13 +3538,13 @@ pub mod tests {
// Setup 2 panes, both with a file open and one with a project search. // Setup 2 panes, both with a file open and one with a project search.
let fs = FakeFs::new(cx.background_executor.clone()); let fs = FakeFs::new(cx.background_executor.clone());
fs.insert_tree( fs.insert_tree(
"/dir", path!("/dir"),
json!({ json!({
"one.rs": "const ONE: usize = 1;", "one.rs": "const ONE: usize = 1;",
}), }),
) )
.await; .await;
let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
let worktree_id = project.update(cx, |this, cx| { let worktree_id = project.update(cx, |this, cx| {
this.worktrees(cx).next().unwrap().read(cx).id() this.worktrees(cx).next().unwrap().read(cx).id()
}); });
@ -3771,13 +3772,13 @@ pub mod tests {
let fs = FakeFs::new(cx.background_executor.clone()); let fs = FakeFs::new(cx.background_executor.clone());
fs.insert_tree( fs.insert_tree(
"/dir", path!("/dir"),
json!({ json!({
"one.rs": "const ONE: usize = 1;", "one.rs": "const ONE: usize = 1;",
}), }),
) )
.await; .await;
let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
let worktree_id = project.update(cx, |this, cx| { let worktree_id = project.update(cx, |this, cx| {
this.worktrees(cx).next().unwrap().read(cx).id() this.worktrees(cx).next().unwrap().read(cx).id()
}); });

View file

@ -44,9 +44,9 @@ sha2.workspace = true
smol.workspace = true smol.workspace = true
theme.workspace = true theme.workspace = true
tree-sitter.workspace = true tree-sitter.workspace = true
ui. workspace = true ui.workspace = true
unindent.workspace = true unindent.workspace = true
util. workspace = true util.workspace = true
workspace.workspace = true workspace.workspace = true
worktree.workspace = true worktree.workspace = true

View file

@ -279,6 +279,7 @@ mod tests {
use settings::SettingsStore; use settings::SettingsStore;
use smol::channel; use smol::channel;
use std::{future, path::Path, sync::Arc}; use std::{future, path::Path, sync::Arc};
use util::separator;
fn init_test(cx: &mut TestAppContext) { fn init_test(cx: &mut TestAppContext) {
env_logger::try_init().ok(); env_logger::try_init().ok();
@ -421,7 +422,10 @@ mod tests {
// Find result that is greater than 0.5 // Find result that is greater than 0.5
let search_result = results.iter().find(|result| result.score > 0.9).unwrap(); let search_result = results.iter().find(|result| result.score > 0.9).unwrap();
assert_eq!(search_result.path.to_string_lossy(), "fixture/needle.md"); assert_eq!(
search_result.path.to_string_lossy(),
separator!("fixture/needle.md")
);
let content = cx let content = cx
.update(|cx| { .update(|cx| {

View file

@ -12,6 +12,7 @@ pub fn test_settings() -> String {
crate::default_settings().as_ref(), crate::default_settings().as_ref(),
) )
.unwrap(); .unwrap();
#[cfg(not(target_os = "windows"))]
util::merge_non_null_json_value_into( util::merge_non_null_json_value_into(
serde_json::json!({ serde_json::json!({
"ui_font_family": "Courier", "ui_font_family": "Courier",
@ -26,6 +27,21 @@ pub fn test_settings() -> String {
}), }),
&mut value, &mut value,
); );
#[cfg(target_os = "windows")]
util::merge_non_null_json_value_into(
serde_json::json!({
"ui_font_family": "Courier New",
"ui_font_features": {},
"ui_font_size": 14,
"ui_font_fallback": [],
"buffer_font_family": "Courier New",
"buffer_font_features": {},
"buffer_font_size": 14,
"buffer_font_fallback": [],
"theme": EMPTY_THEME_NAME,
}),
&mut value,
);
value.as_object_mut().unwrap().remove("languages"); value.as_object_mut().unwrap().remove("languages");
serde_json::to_string(&value).unwrap() serde_json::to_string(&value).unwrap()
} }

View file

@ -16,4 +16,4 @@ doctest = false
[dependencies] [dependencies]
sqlez.workspace = true sqlez.workspace = true
sqlformat.workspace = true sqlformat.workspace = true
syn = "1.0" syn.workspace = true

View file

@ -5,6 +5,7 @@ use menu::SelectPrev;
use project::{Project, ProjectPath}; use project::{Project, ProjectPath};
use serde_json::json; use serde_json::json;
use std::path::Path; use std::path::Path;
use util::path;
use workspace::{AppState, Workspace}; use workspace::{AppState, Workspace};
#[ctor::ctor] #[ctor::ctor]
@ -24,7 +25,7 @@ async fn test_open_with_prev_tab_selected_and_cycle_on_toggle_action(
.fs .fs
.as_fake() .as_fake()
.insert_tree( .insert_tree(
"/root", path!("/root"),
json!({ json!({
"1.txt": "First file", "1.txt": "First file",
"2.txt": "Second file", "2.txt": "Second file",
@ -34,7 +35,7 @@ async fn test_open_with_prev_tab_selected_and_cycle_on_toggle_action(
) )
.await; .await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; let project = Project::test(app_state.fs.clone(), [path!("/root").as_ref()], cx).await;
let (workspace, cx) = let (workspace, cx) =
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx)); cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
@ -81,7 +82,7 @@ async fn test_open_with_last_tab_selected(cx: &mut gpui::TestAppContext) {
.fs .fs
.as_fake() .as_fake()
.insert_tree( .insert_tree(
"/root", path!("/root"),
json!({ json!({
"1.txt": "First file", "1.txt": "First file",
"2.txt": "Second file", "2.txt": "Second file",
@ -90,7 +91,7 @@ async fn test_open_with_last_tab_selected(cx: &mut gpui::TestAppContext) {
) )
.await; .await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; let project = Project::test(app_state.fs.clone(), [path!("/root").as_ref()], cx).await;
let (workspace, cx) = let (workspace, cx) =
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx)); cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
@ -172,10 +173,10 @@ async fn test_open_with_single_item(cx: &mut gpui::TestAppContext) {
app_state app_state
.fs .fs
.as_fake() .as_fake()
.insert_tree("/root", json!({"1.txt": "Single file"})) .insert_tree(path!("/root"), json!({"1.txt": "Single file"}))
.await; .await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; let project = Project::test(app_state.fs.clone(), [path!("/root").as_ref()], cx).await;
let (workspace, cx) = let (workspace, cx) =
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx)); cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
@ -195,7 +196,7 @@ async fn test_close_selected_item(cx: &mut gpui::TestAppContext) {
.fs .fs
.as_fake() .as_fake()
.insert_tree( .insert_tree(
"/root", path!("/root"),
json!({ json!({
"1.txt": "First file", "1.txt": "First file",
"2.txt": "Second file", "2.txt": "Second file",
@ -203,7 +204,7 @@ async fn test_close_selected_item(cx: &mut gpui::TestAppContext) {
) )
.await; .await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; let project = Project::test(app_state.fs.clone(), [path!("/root").as_ref()], cx).await;
let (workspace, cx) = let (workspace, cx) =
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx)); cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
@ -241,7 +242,7 @@ async fn test_close_preserves_selected_position(cx: &mut gpui::TestAppContext) {
.fs .fs
.as_fake() .as_fake()
.insert_tree( .insert_tree(
"/root", path!("/root"),
json!({ json!({
"1.txt": "First file", "1.txt": "First file",
"2.txt": "Second file", "2.txt": "Second file",
@ -250,7 +251,7 @@ async fn test_close_preserves_selected_position(cx: &mut gpui::TestAppContext) {
) )
.await; .await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; let project = Project::test(app_state.fs.clone(), [path!("/root").as_ref()], cx).await;
let (workspace, cx) = let (workspace, cx) =
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx)); cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));

View file

@ -603,6 +603,7 @@ mod tests {
use project::{ContextProviderWithTasks, FakeFs, Project}; use project::{ContextProviderWithTasks, FakeFs, Project};
use serde_json::json; use serde_json::json;
use task::TaskTemplates; use task::TaskTemplates;
use util::path;
use workspace::CloseInactiveTabsAndPanes; use workspace::CloseInactiveTabsAndPanes;
use crate::{modal::Spawn, tests::init_test}; use crate::{modal::Spawn, tests::init_test};
@ -614,7 +615,7 @@ mod tests {
init_test(cx); init_test(cx);
let fs = FakeFs::new(cx.executor()); let fs = FakeFs::new(cx.executor());
fs.insert_tree( fs.insert_tree(
"/dir", path!("/dir"),
json!({ json!({
".zed": { ".zed": {
"tasks.json": r#"[ "tasks.json": r#"[
@ -635,7 +636,7 @@ mod tests {
) )
.await; .await;
let project = Project::test(fs, ["/dir".as_ref()], cx).await; let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
let (workspace, cx) = let (workspace, cx) =
cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx)); cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
@ -654,7 +655,7 @@ mod tests {
let _ = workspace let _ = workspace
.update_in(cx, |workspace, window, cx| { .update_in(cx, |workspace, window, cx| {
workspace.open_abs_path(PathBuf::from("/dir/a.ts"), true, window, cx) workspace.open_abs_path(PathBuf::from(path!("/dir/a.ts")), true, window, cx)
}) })
.await .await
.unwrap(); .unwrap();
@ -778,7 +779,7 @@ mod tests {
init_test(cx); init_test(cx);
let fs = FakeFs::new(cx.executor()); let fs = FakeFs::new(cx.executor());
fs.insert_tree( fs.insert_tree(
"/dir", path!("/dir"),
json!({ json!({
".zed": { ".zed": {
"tasks.json": r#"[ "tasks.json": r#"[
@ -800,7 +801,7 @@ mod tests {
) )
.await; .await;
let project = Project::test(fs, ["/dir".as_ref()], cx).await; let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
let (workspace, cx) = let (workspace, cx) =
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx)); cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
@ -819,7 +820,7 @@ mod tests {
let _ = workspace let _ = workspace
.update_in(cx, |workspace, window, cx| { .update_in(cx, |workspace, window, cx| {
workspace.open_abs_path( workspace.open_abs_path(
PathBuf::from("/dir/file_with.odd_extension"), PathBuf::from(path!("/dir/file_with.odd_extension")),
true, true,
window, window,
cx, cx,
@ -832,8 +833,8 @@ mod tests {
assert_eq!( assert_eq!(
task_names(&tasks_picker, cx), task_names(&tasks_picker, cx),
vec![ vec![
"hello from /dir/file_with.odd_extension:1:1".to_string(), concat!("hello from ", path!("/dir/file_with.odd_extension:1:1")).to_string(),
"opened now: /dir".to_string() concat!("opened now: ", path!("/dir")).to_string(),
], ],
"Second opened buffer should fill the context, labels should be trimmed if long enough" "Second opened buffer should fill the context, labels should be trimmed if long enough"
); );
@ -846,7 +847,7 @@ mod tests {
let second_item = workspace let second_item = workspace
.update_in(cx, |workspace, window, cx| { .update_in(cx, |workspace, window, cx| {
workspace.open_abs_path( workspace.open_abs_path(
PathBuf::from("/dir/file_without_extension"), PathBuf::from(path!("/dir/file_without_extension")),
true, true,
window, window,
cx, cx,
@ -868,8 +869,8 @@ mod tests {
assert_eq!( assert_eq!(
task_names(&tasks_picker, cx), task_names(&tasks_picker, cx),
vec![ vec![
"hello from /dir/file_without_extension:2:3".to_string(), concat!("hello from ", path!("/dir/file_without_extension:2:3")).to_string(),
"opened now: /dir".to_string() concat!("opened now: ", path!("/dir")).to_string(),
], ],
"Opened buffer should fill the context, labels should be trimmed if long enough" "Opened buffer should fill the context, labels should be trimmed if long enough"
); );
@ -885,7 +886,7 @@ mod tests {
init_test(cx); init_test(cx);
let fs = FakeFs::new(cx.executor()); let fs = FakeFs::new(cx.executor());
fs.insert_tree( fs.insert_tree(
"/dir", path!("/dir"),
json!({ json!({
"a1.ts": "// a1", "a1.ts": "// a1",
"a2.ts": "// a2", "a2.ts": "// a2",
@ -894,7 +895,7 @@ mod tests {
) )
.await; .await;
let project = Project::test(fs, ["/dir".as_ref()], cx).await; let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
project.read_with(cx, |project, _| { project.read_with(cx, |project, _| {
let language_registry = project.languages(); let language_registry = project.languages();
language_registry.add(Arc::new( language_registry.add(Arc::new(
@ -955,7 +956,7 @@ mod tests {
let _ts_file_1 = workspace let _ts_file_1 = workspace
.update_in(cx, |workspace, window, cx| { .update_in(cx, |workspace, window, cx| {
workspace.open_abs_path(PathBuf::from("/dir/a1.ts"), true, window, cx) workspace.open_abs_path(PathBuf::from(path!("/dir/a1.ts")), true, window, cx)
}) })
.await .await
.unwrap(); .unwrap();
@ -963,23 +964,28 @@ mod tests {
assert_eq!( assert_eq!(
task_names(&tasks_picker, cx), task_names(&tasks_picker, cx),
vec![ vec![
"Another task from file /dir/a1.ts", concat!("Another task from file ", path!("/dir/a1.ts")),
"TypeScript task from file /dir/a1.ts", concat!("TypeScript task from file ", path!("/dir/a1.ts")),
"Task without variables", "Task without variables",
], ],
"Should open spawn TypeScript tasks for the opened file, tasks with most template variables above, all groups sorted alphanumerically" "Should open spawn TypeScript tasks for the opened file, tasks with most template variables above, all groups sorted alphanumerically"
); );
emulate_task_schedule( emulate_task_schedule(
tasks_picker, tasks_picker,
&project, &project,
"TypeScript task from file /dir/a1.ts", concat!("TypeScript task from file ", path!("/dir/a1.ts")),
cx, cx,
); );
let tasks_picker = open_spawn_tasks(&workspace, cx); let tasks_picker = open_spawn_tasks(&workspace, cx);
assert_eq!( assert_eq!(
task_names(&tasks_picker, cx), task_names(&tasks_picker, cx),
vec!["TypeScript task from file /dir/a1.ts", "Another task from file /dir/a1.ts", "Task without variables"], vec![
concat!("TypeScript task from file ", path!("/dir/a1.ts")),
concat!("Another task from file ", path!("/dir/a1.ts")),
"Task without variables",
],
"After spawning the task and getting it into the history, it should be up in the sort as recently used. "After spawning the task and getting it into the history, it should be up in the sort as recently used.
Tasks with the same labels and context are deduplicated." Tasks with the same labels and context are deduplicated."
); );
@ -991,7 +997,7 @@ mod tests {
let _ts_file_2 = workspace let _ts_file_2 = workspace
.update_in(cx, |workspace, window, cx| { .update_in(cx, |workspace, window, cx| {
workspace.open_abs_path(PathBuf::from("/dir/a2.ts"), true, window, cx) workspace.open_abs_path(PathBuf::from(path!("/dir/a2.ts")), true, window, cx)
}) })
.await .await
.unwrap(); .unwrap();
@ -999,10 +1005,10 @@ mod tests {
assert_eq!( assert_eq!(
task_names(&tasks_picker, cx), task_names(&tasks_picker, cx),
vec![ vec![
"TypeScript task from file /dir/a1.ts", concat!("TypeScript task from file ", path!("/dir/a1.ts")),
"Another task from file /dir/a2.ts", concat!("Another task from file ", path!("/dir/a2.ts")),
"TypeScript task from file /dir/a2.ts", concat!("TypeScript task from file ", path!("/dir/a2.ts")),
"Task without variables" "Task without variables",
], ],
"Even when both TS files are open, should only show the history (on the top), and tasks, resolved for the current file" "Even when both TS files are open, should only show the history (on the top), and tasks, resolved for the current file"
); );
@ -1029,7 +1035,7 @@ mod tests {
emulate_task_schedule(tasks_picker, &project, "Rust task", cx); emulate_task_schedule(tasks_picker, &project, "Rust task", cx);
let _ts_file_2 = workspace let _ts_file_2 = workspace
.update_in(cx, |workspace, window, cx| { .update_in(cx, |workspace, window, cx| {
workspace.open_abs_path(PathBuf::from("/dir/a2.ts"), true, window, cx) workspace.open_abs_path(PathBuf::from(path!("/dir/a2.ts")), true, window, cx)
}) })
.await .await
.unwrap(); .unwrap();
@ -1037,10 +1043,10 @@ mod tests {
assert_eq!( assert_eq!(
task_names(&tasks_picker, cx), task_names(&tasks_picker, cx),
vec![ vec![
"TypeScript task from file /dir/a1.ts", concat!("TypeScript task from file ", path!("/dir/a1.ts")),
"Another task from file /dir/a2.ts", concat!("Another task from file ", path!("/dir/a2.ts")),
"TypeScript task from file /dir/a2.ts", concat!("TypeScript task from file ", path!("/dir/a2.ts")),
"Task without variables" "Task without variables",
], ],
"After closing all but *.rs tabs, running a Rust task and switching back to TS tasks, \ "After closing all but *.rs tabs, running a Rust task and switching back to TS tasks, \
same TS spawn history should be restored" same TS spawn history should be restored"

View file

@ -262,6 +262,7 @@ mod tests {
use serde_json::json; use serde_json::json;
use task::{TaskContext, TaskVariables, VariableName}; use task::{TaskContext, TaskVariables, VariableName};
use ui::VisualContext; use ui::VisualContext;
use util::{path, separator};
use workspace::{AppState, Workspace}; use workspace::{AppState, Workspace};
use crate::task_context; use crate::task_context;
@ -271,7 +272,7 @@ mod tests {
init_test(cx); init_test(cx);
let fs = FakeFs::new(cx.executor()); let fs = FakeFs::new(cx.executor());
fs.insert_tree( fs.insert_tree(
"/dir", path!("/dir"),
json!({ json!({
".zed": { ".zed": {
"tasks.json": r#"[ "tasks.json": r#"[
@ -295,7 +296,7 @@ mod tests {
}), }),
) )
.await; .await;
let project = Project::test(fs, ["/dir".as_ref()], cx).await; let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
let worktree_store = project.update(cx, |project, _| project.worktree_store().clone()); let worktree_store = project.update(cx, |project, _| project.worktree_store().clone());
let rust_language = Arc::new( let rust_language = Arc::new(
Language::new( Language::new(
@ -375,17 +376,18 @@ mod tests {
task_context(workspace, window, cx) task_context(workspace, window, cx)
}) })
.await; .await;
assert_eq!( assert_eq!(
first_context, first_context,
TaskContext { TaskContext {
cwd: Some("/dir".into()), cwd: Some(path!("/dir").into()),
task_variables: TaskVariables::from_iter([ task_variables: TaskVariables::from_iter([
(VariableName::File, "/dir/rust/b.rs".into()), (VariableName::File, path!("/dir/rust/b.rs").into()),
(VariableName::Filename, "b.rs".into()), (VariableName::Filename, "b.rs".into()),
(VariableName::RelativeFile, "rust/b.rs".into()), (VariableName::RelativeFile, separator!("rust/b.rs").into()),
(VariableName::Dirname, "/dir/rust".into()), (VariableName::Dirname, path!("/dir/rust").into()),
(VariableName::Stem, "b".into()), (VariableName::Stem, "b".into()),
(VariableName::WorktreeRoot, "/dir".into()), (VariableName::WorktreeRoot, path!("/dir").into()),
(VariableName::Row, "1".into()), (VariableName::Row, "1".into()),
(VariableName::Column, "1".into()), (VariableName::Column, "1".into()),
]), ]),
@ -407,14 +409,14 @@ mod tests {
}) })
.await, .await,
TaskContext { TaskContext {
cwd: Some("/dir".into()), cwd: Some(path!("/dir").into()),
task_variables: TaskVariables::from_iter([ task_variables: TaskVariables::from_iter([
(VariableName::File, "/dir/rust/b.rs".into()), (VariableName::File, path!("/dir/rust/b.rs").into()),
(VariableName::Filename, "b.rs".into()), (VariableName::Filename, "b.rs".into()),
(VariableName::RelativeFile, "rust/b.rs".into()), (VariableName::RelativeFile, separator!("rust/b.rs").into()),
(VariableName::Dirname, "/dir/rust".into()), (VariableName::Dirname, path!("/dir/rust").into()),
(VariableName::Stem, "b".into()), (VariableName::Stem, "b".into()),
(VariableName::WorktreeRoot, "/dir".into()), (VariableName::WorktreeRoot, path!("/dir").into()),
(VariableName::Row, "1".into()), (VariableName::Row, "1".into()),
(VariableName::Column, "15".into()), (VariableName::Column, "15".into()),
(VariableName::SelectedText, "is_i".into()), (VariableName::SelectedText, "is_i".into()),
@ -433,14 +435,14 @@ mod tests {
}) })
.await, .await,
TaskContext { TaskContext {
cwd: Some("/dir".into()), cwd: Some(path!("/dir").into()),
task_variables: TaskVariables::from_iter([ task_variables: TaskVariables::from_iter([
(VariableName::File, "/dir/a.ts".into()), (VariableName::File, path!("/dir/a.ts").into()),
(VariableName::Filename, "a.ts".into()), (VariableName::Filename, "a.ts".into()),
(VariableName::RelativeFile, "a.ts".into()), (VariableName::RelativeFile, "a.ts".into()),
(VariableName::Dirname, "/dir".into()), (VariableName::Dirname, path!("/dir").into()),
(VariableName::Stem, "a".into()), (VariableName::Stem, "a".into()),
(VariableName::WorktreeRoot, "/dir".into()), (VariableName::WorktreeRoot, path!("/dir").into()),
(VariableName::Row, "1".into()), (VariableName::Row, "1".into()),
(VariableName::Column, "1".into()), (VariableName::Column, "1".into()),
(VariableName::Symbol, "this_is_a_test".into()), (VariableName::Symbol, "this_is_a_test".into()),

View file

@ -13,7 +13,7 @@ path = "src/ui_macros.rs"
proc-macro = true proc-macro = true
[dependencies] [dependencies]
proc-macro2 = "1.0.66" proc-macro2.workspace = true
quote = "1.0.9" quote.workspace = true
syn = { version = "1.0.72", features = ["full", "extra-traits"] } syn.workspace = true
convert_case.workspace = true convert_case.workspace = true

View file

@ -13,7 +13,7 @@ path = "src/util.rs"
doctest = true doctest = true
[features] [features]
test-support = ["tempfile", "git2", "rand"] test-support = ["tempfile", "git2", "rand", "util_macros"]
[dependencies] [dependencies]
anyhow.workspace = true anyhow.workspace = true
@ -35,6 +35,7 @@ smol.workspace = true
take-until.workspace = true take-until.workspace = true
tempfile = { workspace = true, optional = true } tempfile = { workspace = true, optional = true }
unicase.workspace = true unicase.workspace = true
util_macros = { workspace = true, optional = true }
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
libc.workspace = true libc.workspace = true
@ -47,3 +48,4 @@ dunce = "1.0"
git2.workspace = true git2.workspace = true
rand.workspace = true rand.workspace = true
tempfile.workspace = true tempfile.workspace = true
util_macros.workspace = true

View file

@ -23,6 +23,7 @@ pub trait PathExt {
fn compact(&self) -> PathBuf; fn compact(&self) -> PathBuf;
fn icon_stem_or_suffix(&self) -> Option<&str>; fn icon_stem_or_suffix(&self) -> Option<&str>;
fn extension_or_hidden_file_name(&self) -> Option<&str>; fn extension_or_hidden_file_name(&self) -> Option<&str>;
fn to_sanitized_string(&self) -> String;
fn try_from_bytes<'a>(bytes: &'a [u8]) -> anyhow::Result<Self> fn try_from_bytes<'a>(bytes: &'a [u8]) -> anyhow::Result<Self>
where where
Self: From<&'a Path>, Self: From<&'a Path>,
@ -94,6 +95,20 @@ impl<T: AsRef<Path>> PathExt for T {
self.as_ref().file_name()?.to_str()?.split('.').last() self.as_ref().file_name()?.to_str()?.split('.').last()
} }
/// Returns a sanitized string representation of the path.
/// Note, on Windows, this assumes that the path is a valid UTF-8 string and
/// is not a UNC path.
fn to_sanitized_string(&self) -> String {
#[cfg(target_os = "windows")]
{
self.as_ref().to_string_lossy().replace("/", "\\")
}
#[cfg(not(target_os = "windows"))]
{
self.as_ref().to_string_lossy().to_string()
}
}
} }
/// Due to the issue of UNC paths on Windows, which can cause bugs in various parts of Zed, introducing this `SanitizedPath` /// Due to the issue of UNC paths on Windows, which can cause bugs in various parts of Zed, introducing this `SanitizedPath`
@ -115,6 +130,17 @@ impl SanitizedPath {
self.0.to_string_lossy().to_string() self.0.to_string_lossy().to_string()
} }
pub fn to_glob_string(&self) -> String {
#[cfg(target_os = "windows")]
{
self.0.to_string_lossy().replace("/", "\\")
}
#[cfg(not(target_os = "windows"))]
{
self.0.to_string_lossy().to_string()
}
}
pub fn join(&self, path: &Self) -> Self { pub fn join(&self, path: &Self) -> Self {
self.0.join(&path.0).into() self.0.join(&path.0).into()
} }
@ -448,14 +474,6 @@ pub fn compare_paths(
} }
} }
#[cfg(any(test, feature = "test-support"))]
pub fn replace_path_separator(path: &str) -> String {
#[cfg(target_os = "windows")]
return path.replace("/", std::path::MAIN_SEPARATOR_STR);
#[cfg(not(target_os = "windows"))]
return path.to_string();
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View file

@ -28,6 +28,8 @@ use unicase::UniCase;
use anyhow::{anyhow, Context as _}; use anyhow::{anyhow, Context as _};
pub use take_until::*; pub use take_until::*;
#[cfg(any(test, feature = "test-support"))]
pub use util_macros::{separator, uri};
#[macro_export] #[macro_export]
macro_rules! debug_panic { macro_rules! debug_panic {
@ -41,6 +43,50 @@ macro_rules! debug_panic {
}; };
} }
/// A macro to add "C:" to the beginning of a path literal on Windows, and replace all
/// the separator from `/` to `\`.
/// But on non-Windows platforms, it will return the path literal as is.
///
/// # Examples
/// ```rust
/// use util::path;
///
/// let path = path!("/Users/user/file.txt");
/// #[cfg(target_os = "windows")]
/// assert_eq!(path, "C:\\Users\\user\\file.txt");
/// #[cfg(not(target_os = "windows"))]
/// assert_eq!(path, "/Users/user/file.txt");
/// ```
#[cfg(all(any(test, feature = "test-support"), target_os = "windows"))]
#[macro_export]
macro_rules! path {
($path:literal) => {
concat!("C:", util::separator!($path))
};
}
/// A macro to add "C:" to the beginning of a path literal on Windows, and replace all
/// the separator from `/` to `\`.
/// But on non-Windows platforms, it will return the path literal as is.
///
/// # Examples
/// ```rust
/// use util::path;
///
/// let path = path!("/Users/user/file.txt");
/// #[cfg(target_os = "windows")]
/// assert_eq!(path, "C:\\Users\\user\\file.txt");
/// #[cfg(not(target_os = "windows"))]
/// assert_eq!(path, "/Users/user/file.txt");
/// ```
#[cfg(all(any(test, feature = "test-support"), not(target_os = "windows")))]
#[macro_export]
macro_rules! path {
($path:literal) => {
$path
};
}
pub fn truncate(s: &str, max_chars: usize) -> &str { pub fn truncate(s: &str, max_chars: usize) -> &str {
match s.char_indices().nth(max_chars) { match s.char_indices().nth(max_chars) {
None => s, None => s,

View file

@ -0,0 +1,18 @@
[package]
name = "util_macros"
version = "0.1.0"
edition.workspace = true
publish.workspace = true
license = "GPL-3.0-or-later"
[lints]
workspace = true
[lib]
path = "src/util_macros.rs"
proc-macro = true
doctest = false
[dependencies]
quote.workspace = true
syn.workspace = true

View file

@ -0,0 +1 @@
../../LICENSE-APACHE

View file

@ -0,0 +1,56 @@
#![cfg_attr(not(target_os = "windows"), allow(unused))]
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, LitStr};
/// This macro replaces the path separator `/` with `\` for Windows.
/// But if the target OS is not Windows, the path is returned as is.
///
/// # Example
/// ```rust
/// # use util_macros::separator;
/// let path = separator!("path/to/file");
/// #[cfg(target_os = "windows")]
/// assert_eq!(path, "path\\to\\file");
/// #[cfg(not(target_os = "windows"))]
/// assert_eq!(path, "path/to/file");
/// ```
#[proc_macro]
pub fn separator(input: TokenStream) -> TokenStream {
let path = parse_macro_input!(input as LitStr);
let path = path.value();
#[cfg(target_os = "windows")]
let path = path.replace("/", "\\");
TokenStream::from(quote! {
#path
})
}
/// This macro replaces the path prefix `file:///` with `file:///C:/` for Windows.
/// But if the target OS is not Windows, the URI is returned as is.
///
/// # Example
/// ```rust
/// use util_macros::uri;
///
/// let uri = uri!("file:///path/to/file");
/// #[cfg(target_os = "windows")]
/// assert_eq!(uri, "file:///C:/path/to/file");
/// #[cfg(not(target_os = "windows"))]
/// assert_eq!(uri, "file:///path/to/file");
/// ```
#[proc_macro]
pub fn uri(input: TokenStream) -> TokenStream {
let uri = parse_macro_input!(input as LitStr);
let uri = uri.value();
#[cfg(target_os = "windows")]
let uri = uri.replace("file:///", "file:///C:/");
TokenStream::from(quote! {
#uri
})
}

View file

@ -1455,6 +1455,7 @@ mod test {
use editor::Editor; use editor::Editor;
use gpui::{Context, TestAppContext}; use gpui::{Context, TestAppContext};
use indoc::indoc; use indoc::indoc;
use util::path;
use workspace::Workspace; use workspace::Workspace;
#[gpui::test] #[gpui::test]
@ -1551,13 +1552,13 @@ mod test {
#[gpui::test] #[gpui::test]
async fn test_command_write(cx: &mut TestAppContext) { async fn test_command_write(cx: &mut TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await; let mut cx = VimTestContext::new(cx, true).await;
let path = Path::new("/root/dir/file.rs"); let path = Path::new(path!("/root/dir/file.rs"));
let fs = cx.workspace(|workspace, _, cx| workspace.project().read(cx).fs().clone()); let fs = cx.workspace(|workspace, _, cx| workspace.project().read(cx).fs().clone());
cx.simulate_keystrokes("i @ escape"); cx.simulate_keystrokes("i @ escape");
cx.simulate_keystrokes(": w enter"); cx.simulate_keystrokes(": w enter");
assert_eq!(fs.load(path).await.unwrap(), "@\n"); assert_eq!(fs.load(path).await.unwrap().replace("\r\n", "\n"), "@\n");
fs.as_fake().insert_file(path, b"oops\n".to_vec()).await; fs.as_fake().insert_file(path, b"oops\n".to_vec()).await;
@ -1567,12 +1568,12 @@ mod test {
assert!(cx.has_pending_prompt()); assert!(cx.has_pending_prompt());
// "Cancel" // "Cancel"
cx.simulate_prompt_answer(0); cx.simulate_prompt_answer(0);
assert_eq!(fs.load(path).await.unwrap(), "oops\n"); assert_eq!(fs.load(path).await.unwrap().replace("\r\n", "\n"), "oops\n");
assert!(!cx.has_pending_prompt()); assert!(!cx.has_pending_prompt());
// force overwrite // force overwrite
cx.simulate_keystrokes(": w ! enter"); cx.simulate_keystrokes(": w ! enter");
assert!(!cx.has_pending_prompt()); assert!(!cx.has_pending_prompt());
assert_eq!(fs.load(path).await.unwrap(), "@@\n"); assert_eq!(fs.load(path).await.unwrap().replace("\r\n", "\n"), "@@\n");
} }
#[gpui::test] #[gpui::test]
@ -1664,7 +1665,7 @@ mod test {
let file_path = file.as_local().unwrap().abs_path(cx); let file_path = file.as_local().unwrap().abs_path(cx);
assert_eq!(text, expected_text); assert_eq!(text, expected_text);
assert_eq!(file_path.to_str().unwrap(), expected_path); assert_eq!(file_path, Path::new(expected_path));
} }
#[gpui::test] #[gpui::test]
@ -1673,16 +1674,22 @@ mod test {
// Assert base state, that we're in /root/dir/file.rs // Assert base state, that we're in /root/dir/file.rs
cx.workspace(|workspace, _, cx| { cx.workspace(|workspace, _, cx| {
assert_active_item(workspace, "/root/dir/file.rs", "", cx); assert_active_item(workspace, path!("/root/dir/file.rs"), "", cx);
}); });
// Insert a new file // Insert a new file
let fs = cx.workspace(|workspace, _, cx| workspace.project().read(cx).fs().clone()); let fs = cx.workspace(|workspace, _, cx| workspace.project().read(cx).fs().clone());
fs.as_fake() fs.as_fake()
.insert_file("/root/dir/file2.rs", "This is file2.rs".as_bytes().to_vec()) .insert_file(
path!("/root/dir/file2.rs"),
"This is file2.rs".as_bytes().to_vec(),
)
.await; .await;
fs.as_fake() fs.as_fake()
.insert_file("/root/dir/file3.rs", "go to file3".as_bytes().to_vec()) .insert_file(
path!("/root/dir/file3.rs"),
"go to file3".as_bytes().to_vec(),
)
.await; .await;
// Put the path to the second file into the currently open buffer // Put the path to the second file into the currently open buffer
@ -1694,7 +1701,12 @@ mod test {
// We now have two items // We now have two items
cx.workspace(|workspace, _, cx| assert_eq!(workspace.items(cx).count(), 2)); cx.workspace(|workspace, _, cx| assert_eq!(workspace.items(cx).count(), 2));
cx.workspace(|workspace, _, cx| { cx.workspace(|workspace, _, cx| {
assert_active_item(workspace, "/root/dir/file2.rs", "This is file2.rs", cx); assert_active_item(
workspace,
path!("/root/dir/file2.rs"),
"This is file2.rs",
cx,
);
}); });
// Update editor to point to `file2.rs` // Update editor to point to `file2.rs`
@ -1711,7 +1723,7 @@ mod test {
// We now have three items // We now have three items
cx.workspace(|workspace, _, cx| assert_eq!(workspace.items(cx).count(), 3)); cx.workspace(|workspace, _, cx| assert_eq!(workspace.items(cx).count(), 3));
cx.workspace(|workspace, _, cx| { cx.workspace(|workspace, _, cx| {
assert_active_item(workspace, "/root/dir/file3.rs", "go to file3", cx); assert_active_item(workspace, path!("/root/dir/file3.rs"), "go to file3", cx);
}); });
} }

View file

@ -696,12 +696,20 @@ mod test {
// not testing nvim as it doesn't have a filename // not testing nvim as it doesn't have a filename
cx.simulate_keystrokes("\" % p"); cx.simulate_keystrokes("\" % p");
#[cfg(not(target_os = "windows"))]
cx.assert_state( cx.assert_state(
indoc! {" indoc! {"
The quick brown The quick brown
dogdir/file.rˇs"}, dogdir/file.rˇs"},
Mode::Normal, Mode::Normal,
); );
#[cfg(target_os = "windows")]
cx.assert_state(
indoc! {"
The quick brown
dogdir\\file.rˇs"},
Mode::Normal,
);
} }
#[gpui::test] #[gpui::test]

View file

@ -1319,14 +1319,7 @@ impl LocalWorktree {
let settings = self.settings.clone(); let settings = self.settings.clone();
let (scan_states_tx, mut scan_states_rx) = mpsc::unbounded(); let (scan_states_tx, mut scan_states_rx) = mpsc::unbounded();
let background_scanner = cx.background_executor().spawn({ let background_scanner = cx.background_executor().spawn({
let abs_path = &snapshot.abs_path; let abs_path = snapshot.abs_path.as_path().to_path_buf();
#[cfg(target_os = "windows")]
let abs_path = abs_path
.as_path()
.canonicalize()
.unwrap_or_else(|_| abs_path.as_path().to_path_buf());
#[cfg(not(target_os = "windows"))]
let abs_path = abs_path.as_path().to_path_buf();
let background = cx.background_executor().clone(); let background = cx.background_executor().clone();
async move { async move {
let (events, watcher) = fs.watch(&abs_path, FS_WATCH_LATENCY).await; let (events, watcher) = fs.watch(&abs_path, FS_WATCH_LATENCY).await;

View file

@ -2156,7 +2156,13 @@ const CONFLICT: FileStatus = FileStatus::Unmerged(UnmergedStatus {
second_head: UnmergedStatusCode::Updated, second_head: UnmergedStatusCode::Updated,
}); });
// NOTE:
// This test always fails on Windows, because on Windows, unlike on Unix, you can't rename
// a directory which some program has already open.
// This is a limitation of the Windows.
// See: https://stackoverflow.com/questions/41365318/access-is-denied-when-renaming-folder
#[gpui::test] #[gpui::test]
#[cfg_attr(target_os = "windows", ignore)]
async fn test_rename_work_directory(cx: &mut TestAppContext) { async fn test_rename_work_directory(cx: &mut TestAppContext) {
init_test(cx); init_test(cx);
cx.executor().allow_parking(); cx.executor().allow_parking();
@ -2184,7 +2190,7 @@ async fn test_rename_work_directory(cx: &mut TestAppContext) {
let repo = git_init(&root_path.join("projects/project1")); let repo = git_init(&root_path.join("projects/project1"));
git_add("a", &repo); git_add("a", &repo);
git_commit("init", &repo); git_commit("init", &repo);
std::fs::write(root_path.join("projects/project1/a"), "aa").ok(); std::fs::write(root_path.join("projects/project1/a"), "aa").unwrap();
cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete()) cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
.await; .await;
@ -2209,7 +2215,7 @@ async fn test_rename_work_directory(cx: &mut TestAppContext) {
root_path.join("projects/project1"), root_path.join("projects/project1"),
root_path.join("projects/project2"), root_path.join("projects/project2"),
) )
.ok(); .unwrap();
tree.flush_fs_events(cx).await; tree.flush_fs_events(cx).await;
cx.read(|cx| { cx.read(|cx| {
@ -2335,7 +2341,13 @@ async fn test_git_repository_for_path(cx: &mut TestAppContext) {
}); });
} }
// NOTE:
// This test always fails on Windows, because on Windows, unlike on Unix, you can't rename
// a directory which some program has already open.
// This is a limitation of the Windows.
// See: https://stackoverflow.com/questions/41365318/access-is-denied-when-renaming-folder
#[gpui::test] #[gpui::test]
#[cfg_attr(target_os = "windows", ignore)]
async fn test_file_status(cx: &mut TestAppContext) { async fn test_file_status(cx: &mut TestAppContext) {
init_test(cx); init_test(cx);
cx.executor().allow_parking(); cx.executor().allow_parking();

View file

@ -1569,6 +1569,7 @@ mod tests {
time::Duration, time::Duration,
}; };
use theme::{ThemeRegistry, ThemeSettings}; use theme::{ThemeRegistry, ThemeSettings};
use util::{path, separator};
use workspace::{ use workspace::{
item::{Item, ItemHandle}, item::{Item, ItemHandle},
open_new, open_paths, pane, NewFile, OpenVisible, SaveIntent, SplitDirection, open_new, open_paths, pane, NewFile, OpenVisible, SaveIntent, SplitDirection,
@ -1737,12 +1738,15 @@ mod tests {
app_state app_state
.fs .fs
.as_fake() .as_fake()
.insert_tree("/root", json!({"a": "hey", "b": "", "dir": {"c": "f"}})) .insert_tree(
path!("/root"),
json!({"a": "hey", "b": "", "dir": {"c": "f"}}),
)
.await; .await;
cx.update(|cx| { cx.update(|cx| {
open_paths( open_paths(
&[PathBuf::from("/root/dir")], &[PathBuf::from(path!("/root/dir"))],
app_state.clone(), app_state.clone(),
workspace::OpenOptions::default(), workspace::OpenOptions::default(),
cx, cx,
@ -1754,7 +1758,7 @@ mod tests {
cx.update(|cx| { cx.update(|cx| {
open_paths( open_paths(
&[PathBuf::from("/root/a")], &[PathBuf::from(path!("/root/a"))],
app_state.clone(), app_state.clone(),
workspace::OpenOptions { workspace::OpenOptions {
open_new_workspace: Some(false), open_new_workspace: Some(false),
@ -1769,7 +1773,7 @@ mod tests {
cx.update(|cx| { cx.update(|cx| {
open_paths( open_paths(
&[PathBuf::from("/root/dir/c")], &[PathBuf::from(path!("/root/dir/c"))],
app_state.clone(), app_state.clone(),
workspace::OpenOptions { workspace::OpenOptions {
open_new_workspace: Some(true), open_new_workspace: Some(true),
@ -1789,12 +1793,15 @@ mod tests {
app_state app_state
.fs .fs
.as_fake() .as_fake()
.insert_tree("/root", json!({"dir1": {"a": "b"}, "dir2": {"c": "d"}})) .insert_tree(
path!("/root"),
json!({"dir1": {"a": "b"}, "dir2": {"c": "d"}}),
)
.await; .await;
cx.update(|cx| { cx.update(|cx| {
open_paths( open_paths(
&[PathBuf::from("/root/dir1/a")], &[PathBuf::from(path!("/root/dir1/a"))],
app_state.clone(), app_state.clone(),
workspace::OpenOptions::default(), workspace::OpenOptions::default(),
cx, cx,
@ -1807,7 +1814,7 @@ mod tests {
cx.update(|cx| { cx.update(|cx| {
open_paths( open_paths(
&[PathBuf::from("/root/dir2/c")], &[PathBuf::from(path!("/root/dir2/c"))],
app_state.clone(), app_state.clone(),
workspace::OpenOptions::default(), workspace::OpenOptions::default(),
cx, cx,
@ -1819,7 +1826,7 @@ mod tests {
cx.update(|cx| { cx.update(|cx| {
open_paths( open_paths(
&[PathBuf::from("/root/dir2")], &[PathBuf::from(path!("/root/dir2"))],
app_state.clone(), app_state.clone(),
workspace::OpenOptions::default(), workspace::OpenOptions::default(),
cx, cx,
@ -1835,7 +1842,7 @@ mod tests {
cx.update(|cx| { cx.update(|cx| {
open_paths( open_paths(
&[PathBuf::from("/root/dir2/c")], &[PathBuf::from(path!("/root/dir2/c"))],
app_state.clone(), app_state.clone(),
workspace::OpenOptions::default(), workspace::OpenOptions::default(),
cx, cx,
@ -1864,12 +1871,12 @@ mod tests {
app_state app_state
.fs .fs
.as_fake() .as_fake()
.insert_tree("/root", json!({"a": "hey"})) .insert_tree(path!("/root"), json!({"a": "hey"}))
.await; .await;
cx.update(|cx| { cx.update(|cx| {
open_paths( open_paths(
&[PathBuf::from("/root/a")], &[PathBuf::from(path!("/root/a"))],
app_state.clone(), app_state.clone(),
workspace::OpenOptions::default(), workspace::OpenOptions::default(),
cx, cx,
@ -1951,7 +1958,7 @@ mod tests {
// Opening the buffer again doesn't impact the window's edited state. // Opening the buffer again doesn't impact the window's edited state.
cx.update(|cx| { cx.update(|cx| {
open_paths( open_paths(
&[PathBuf::from("/root/a")], &[PathBuf::from(path!("/root/a"))],
app_state, app_state,
workspace::OpenOptions::default(), workspace::OpenOptions::default(),
cx, cx,
@ -2013,12 +2020,12 @@ mod tests {
app_state app_state
.fs .fs
.as_fake() .as_fake()
.insert_tree("/root", json!({"a": "hey"})) .insert_tree(path!("/root"), json!({"a": "hey"}))
.await; .await;
cx.update(|cx| { cx.update(|cx| {
open_paths( open_paths(
&[PathBuf::from("/root/a")], &[PathBuf::from(path!("/root/a"))],
app_state.clone(), app_state.clone(),
workspace::OpenOptions::default(), workspace::OpenOptions::default(),
cx, cx,
@ -2070,7 +2077,7 @@ mod tests {
// When we now reopen the window, the edited state and the edited buffer are back // When we now reopen the window, the edited state and the edited buffer are back
cx.update(|cx| { cx.update(|cx| {
open_paths( open_paths(
&[PathBuf::from("/root/a")], &[PathBuf::from(path!("/root/a"))],
app_state.clone(), app_state.clone(),
workspace::OpenOptions::default(), workspace::OpenOptions::default(),
cx, cx,
@ -2166,7 +2173,7 @@ mod tests {
.fs .fs
.as_fake() .as_fake()
.insert_tree( .insert_tree(
"/root", path!("/root"),
json!({ json!({
"a": { "a": {
"file1": "contents 1", "file1": "contents 1",
@ -2177,7 +2184,7 @@ mod tests {
) )
.await; .await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; let project = Project::test(app_state.fs.clone(), [path!("/root").as_ref()], cx).await;
project.update(cx, |project, _cx| { project.update(cx, |project, _cx| {
project.languages().add(markdown_language()) project.languages().add(markdown_language())
}); });
@ -2298,7 +2305,7 @@ mod tests {
.fs .fs
.as_fake() .as_fake()
.insert_tree( .insert_tree(
"/", path!("/"),
json!({ json!({
"dir1": { "dir1": {
"a.txt": "" "a.txt": ""
@ -2316,7 +2323,7 @@ mod tests {
cx.update(|cx| { cx.update(|cx| {
open_paths( open_paths(
&[PathBuf::from("/dir1/")], &[PathBuf::from(path!("/dir1/"))],
app_state, app_state,
workspace::OpenOptions::default(), workspace::OpenOptions::default(),
cx, cx,
@ -2363,7 +2370,7 @@ mod tests {
window window
.update(cx, |workspace, window, cx| { .update(cx, |workspace, window, cx| {
workspace.open_paths( workspace.open_paths(
vec!["/dir1/a.txt".into()], vec![path!("/dir1/a.txt").into()],
OpenVisible::All, OpenVisible::All,
None, None,
window, window,
@ -2374,7 +2381,12 @@ mod tests {
.await; .await;
cx.read(|cx| { cx.read(|cx| {
let workspace = workspace.read(cx); let workspace = workspace.read(cx);
assert_project_panel_selection(workspace, Path::new("/dir1"), Path::new("a.txt"), cx); assert_project_panel_selection(
workspace,
Path::new(path!("/dir1")),
Path::new("a.txt"),
cx,
);
assert_eq!( assert_eq!(
workspace workspace
.active_pane() .active_pane()
@ -2393,7 +2405,7 @@ mod tests {
window window
.update(cx, |workspace, window, cx| { .update(cx, |workspace, window, cx| {
workspace.open_paths( workspace.open_paths(
vec!["/dir2/b.txt".into()], vec![path!("/dir2/b.txt").into()],
OpenVisible::All, OpenVisible::All,
None, None,
window, window,
@ -2404,14 +2416,19 @@ mod tests {
.await; .await;
cx.read(|cx| { cx.read(|cx| {
let workspace = workspace.read(cx); let workspace = workspace.read(cx);
assert_project_panel_selection(workspace, Path::new("/dir2/b.txt"), Path::new(""), cx); assert_project_panel_selection(
workspace,
Path::new(path!("/dir2/b.txt")),
Path::new(""),
cx,
);
let worktree_roots = workspace let worktree_roots = workspace
.worktrees(cx) .worktrees(cx)
.map(|w| w.read(cx).as_local().unwrap().abs_path().as_ref()) .map(|w| w.read(cx).as_local().unwrap().abs_path().as_ref())
.collect::<HashSet<_>>(); .collect::<HashSet<_>>();
assert_eq!( assert_eq!(
worktree_roots, worktree_roots,
vec!["/dir1", "/dir2/b.txt"] vec![path!("/dir1"), path!("/dir2/b.txt")]
.into_iter() .into_iter()
.map(Path::new) .map(Path::new)
.collect(), .collect(),
@ -2434,7 +2451,7 @@ mod tests {
window window
.update(cx, |workspace, window, cx| { .update(cx, |workspace, window, cx| {
workspace.open_paths( workspace.open_paths(
vec!["/dir3".into(), "/dir3/c.txt".into()], vec![path!("/dir3").into(), path!("/dir3/c.txt").into()],
OpenVisible::All, OpenVisible::All,
None, None,
window, window,
@ -2445,14 +2462,19 @@ mod tests {
.await; .await;
cx.read(|cx| { cx.read(|cx| {
let workspace = workspace.read(cx); let workspace = workspace.read(cx);
assert_project_panel_selection(workspace, Path::new("/dir3"), Path::new("c.txt"), cx); assert_project_panel_selection(
workspace,
Path::new(path!("/dir3")),
Path::new("c.txt"),
cx,
);
let worktree_roots = workspace let worktree_roots = workspace
.worktrees(cx) .worktrees(cx)
.map(|w| w.read(cx).as_local().unwrap().abs_path().as_ref()) .map(|w| w.read(cx).as_local().unwrap().abs_path().as_ref())
.collect::<HashSet<_>>(); .collect::<HashSet<_>>();
assert_eq!( assert_eq!(
worktree_roots, worktree_roots,
vec!["/dir1", "/dir2/b.txt", "/dir3"] vec![path!("/dir1"), path!("/dir2/b.txt"), path!("/dir3")]
.into_iter() .into_iter()
.map(Path::new) .map(Path::new)
.collect(), .collect(),
@ -2474,23 +2496,39 @@ mod tests {
// Ensure opening invisibly a file outside an existing worktree adds a new, invisible worktree. // Ensure opening invisibly a file outside an existing worktree adds a new, invisible worktree.
window window
.update(cx, |workspace, window, cx| { .update(cx, |workspace, window, cx| {
workspace.open_paths(vec!["/d.txt".into()], OpenVisible::None, None, window, cx) workspace.open_paths(
vec![path!("/d.txt").into()],
OpenVisible::None,
None,
window,
cx,
)
}) })
.unwrap() .unwrap()
.await; .await;
cx.read(|cx| { cx.read(|cx| {
let workspace = workspace.read(cx); let workspace = workspace.read(cx);
assert_project_panel_selection(workspace, Path::new("/d.txt"), Path::new(""), cx); assert_project_panel_selection(
workspace,
Path::new(path!("/d.txt")),
Path::new(""),
cx,
);
let worktree_roots = workspace let worktree_roots = workspace
.worktrees(cx) .worktrees(cx)
.map(|w| w.read(cx).as_local().unwrap().abs_path().as_ref()) .map(|w| w.read(cx).as_local().unwrap().abs_path().as_ref())
.collect::<HashSet<_>>(); .collect::<HashSet<_>>();
assert_eq!( assert_eq!(
worktree_roots, worktree_roots,
vec!["/dir1", "/dir2/b.txt", "/dir3", "/d.txt"] vec![
.into_iter() path!("/dir1"),
.map(Path::new) path!("/dir2/b.txt"),
.collect(), path!("/dir3"),
path!("/d.txt")
]
.into_iter()
.map(Path::new)
.collect(),
); );
let visible_worktree_roots = workspace let visible_worktree_roots = workspace
@ -2499,7 +2537,7 @@ mod tests {
.collect::<HashSet<_>>(); .collect::<HashSet<_>>();
assert_eq!( assert_eq!(
visible_worktree_roots, visible_worktree_roots,
vec!["/dir1", "/dir2/b.txt", "/dir3"] vec![path!("/dir1"), path!("/dir2/b.txt"), path!("/dir3")]
.into_iter() .into_iter()
.map(Path::new) .map(Path::new)
.collect(), .collect(),
@ -2535,7 +2573,7 @@ mod tests {
.fs .fs
.as_fake() .as_fake()
.insert_tree( .insert_tree(
"/root", path!("/root"),
json!({ json!({
".gitignore": "ignored_dir\n", ".gitignore": "ignored_dir\n",
".git": { ".git": {
@ -2560,7 +2598,7 @@ mod tests {
) )
.await; .await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; let project = Project::test(app_state.fs.clone(), [path!("/root").as_ref()], cx).await;
project.update(cx, |project, _cx| { project.update(cx, |project, _cx| {
project.languages().add(markdown_language()) project.languages().add(markdown_language())
}); });
@ -2569,9 +2607,9 @@ mod tests {
let initial_entries = cx.read(|cx| workspace.file_project_paths(cx)); let initial_entries = cx.read(|cx| workspace.file_project_paths(cx));
let paths_to_open = [ let paths_to_open = [
Path::new("/root/excluded_dir/file").to_path_buf(), PathBuf::from(path!("/root/excluded_dir/file")),
Path::new("/root/.git/HEAD").to_path_buf(), PathBuf::from(path!("/root/.git/HEAD")),
Path::new("/root/excluded_dir/ignored_subdir").to_path_buf(), PathBuf::from(path!("/root/excluded_dir/ignored_subdir")),
]; ];
let (opened_workspace, new_items) = cx let (opened_workspace, new_items) = cx
.update(|cx| { .update(|cx| {
@ -2616,8 +2654,8 @@ mod tests {
opened_paths, opened_paths,
vec![ vec![
None, None,
Some(".git/HEAD".to_string()), Some(separator!(".git/HEAD").to_string()),
Some("excluded_dir/file".to_string()), Some(separator!("excluded_dir/file").to_string()),
], ],
"Excluded files should get opened, excluded dir should not get opened" "Excluded files should get opened, excluded dir should not get opened"
); );
@ -2643,7 +2681,7 @@ mod tests {
opened_buffer_paths.sort(); opened_buffer_paths.sort();
assert_eq!( assert_eq!(
opened_buffer_paths, opened_buffer_paths,
vec![".git/HEAD".to_string(), "excluded_dir/file".to_string()], vec![separator!(".git/HEAD").to_string(), separator!("excluded_dir/file").to_string()],
"Despite not being present in the worktrees, buffers for excluded files are opened and added to the pane" "Despite not being present in the worktrees, buffers for excluded files are opened and added to the pane"
); );
}); });
@ -2655,10 +2693,10 @@ mod tests {
app_state app_state
.fs .fs
.as_fake() .as_fake()
.insert_tree("/root", json!({ "a.txt": "" })) .insert_tree(path!("/root"), json!({ "a.txt": "" }))
.await; .await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; let project = Project::test(app_state.fs.clone(), [path!("/root").as_ref()], cx).await;
project.update(cx, |project, _cx| { project.update(cx, |project, _cx| {
project.languages().add(markdown_language()) project.languages().add(markdown_language())
}); });
@ -2669,7 +2707,7 @@ mod tests {
window window
.update(cx, |workspace, window, cx| { .update(cx, |workspace, window, cx| {
workspace.open_paths( workspace.open_paths(
vec![PathBuf::from("/root/a.txt")], vec![PathBuf::from(path!("/root/a.txt"))],
OpenVisible::All, OpenVisible::All,
None, None,
window, window,
@ -2693,7 +2731,7 @@ mod tests {
app_state app_state
.fs .fs
.as_fake() .as_fake()
.insert_file("/root/a.txt", b"changed".to_vec()) .insert_file(path!("/root/a.txt"), b"changed".to_vec())
.await; .await;
cx.run_until_parked(); cx.run_until_parked();
@ -2721,9 +2759,13 @@ mod tests {
#[gpui::test] #[gpui::test]
async fn test_open_and_save_new_file(cx: &mut TestAppContext) { async fn test_open_and_save_new_file(cx: &mut TestAppContext) {
let app_state = init_test(cx); let app_state = init_test(cx);
app_state.fs.create_dir(Path::new("/root")).await.unwrap(); app_state
.fs
.create_dir(Path::new(path!("/root")))
.await
.unwrap();
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; let project = Project::test(app_state.fs.clone(), [path!("/root").as_ref()], cx).await;
project.update(cx, |project, _| { project.update(cx, |project, _| {
project.languages().add(markdown_language()); project.languages().add(markdown_language());
project.languages().add(rust_lang()); project.languages().add(rust_lang());
@ -2766,7 +2808,7 @@ mod tests {
.unwrap(); .unwrap();
cx.background_executor.run_until_parked(); cx.background_executor.run_until_parked();
cx.simulate_new_path_selection(|parent_dir| { cx.simulate_new_path_selection(|parent_dir| {
assert_eq!(parent_dir, Path::new("/root")); assert_eq!(parent_dir, Path::new(path!("/root")));
Some(parent_dir.join("the-new-name.rs")) Some(parent_dir.join("the-new-name.rs"))
}); });
cx.read(|cx| { cx.read(|cx| {
@ -2922,7 +2964,7 @@ mod tests {
.fs .fs
.as_fake() .as_fake()
.insert_tree( .insert_tree(
"/root", path!("/root"),
json!({ json!({
"a": { "a": {
"file1": "contents 1", "file1": "contents 1",
@ -2933,7 +2975,7 @@ mod tests {
) )
.await; .await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; let project = Project::test(app_state.fs.clone(), [path!("/root").as_ref()], cx).await;
project.update(cx, |project, _cx| { project.update(cx, |project, _cx| {
project.languages().add(markdown_language()) project.languages().add(markdown_language())
}); });
@ -3020,7 +3062,7 @@ mod tests {
.fs .fs
.as_fake() .as_fake()
.insert_tree( .insert_tree(
"/root", path!("/root"),
json!({ json!({
"a": { "a": {
"file1": "contents 1\n".repeat(20), "file1": "contents 1\n".repeat(20),
@ -3031,7 +3073,7 @@ mod tests {
) )
.await; .await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; let project = Project::test(app_state.fs.clone(), [path!("/root").as_ref()], cx).await;
project.update(cx, |project, _cx| { project.update(cx, |project, _cx| {
project.languages().add(markdown_language()) project.languages().add(markdown_language())
}); });
@ -3262,7 +3304,7 @@ mod tests {
.unwrap(); .unwrap();
app_state app_state
.fs .fs
.remove_file(Path::new("/root/a/file2"), Default::default()) .remove_file(Path::new(path!("/root/a/file2")), Default::default())
.await .await
.unwrap(); .unwrap();
cx.background_executor.run_until_parked(); cx.background_executor.run_until_parked();
@ -3403,7 +3445,7 @@ mod tests {
.fs .fs
.as_fake() .as_fake()
.insert_tree( .insert_tree(
"/root", path!("/root"),
json!({ json!({
"a": { "a": {
"file1": "", "file1": "",
@ -3415,7 +3457,7 @@ mod tests {
) )
.await; .await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; let project = Project::test(app_state.fs.clone(), [path!("/root").as_ref()], cx).await;
project.update(cx, |project, _cx| { project.update(cx, |project, _cx| {
project.languages().add(markdown_language()) project.languages().add(markdown_language())
}); });

View file

@ -535,6 +535,7 @@ mod tests {
use editor::Editor; use editor::Editor;
use gpui::TestAppContext; use gpui::TestAppContext;
use serde_json::json; use serde_json::json;
use util::path;
use workspace::{AppState, Workspace}; use workspace::{AppState, Workspace};
use crate::zed::{open_listener::open_local_workspace, tests::init_test}; use crate::zed::{open_listener::open_local_workspace, tests::init_test};
@ -547,7 +548,7 @@ mod tests {
.fs .fs
.as_fake() .as_fake()
.insert_tree( .insert_tree(
"/root", path!("/root"),
json!({ json!({
"dir1": { "dir1": {
"file1.txt": "content1", "file1.txt": "content1",
@ -560,7 +561,7 @@ mod tests {
assert_eq!(cx.windows().len(), 0); assert_eq!(cx.windows().len(), 0);
// First open the workspace directory // First open the workspace directory
open_workspace_file("/root/dir1", None, app_state.clone(), cx).await; open_workspace_file(path!("/root/dir1"), None, app_state.clone(), cx).await;
assert_eq!(cx.windows().len(), 1); assert_eq!(cx.windows().len(), 1);
let workspace = cx.windows()[0].downcast::<Workspace>().unwrap(); let workspace = cx.windows()[0].downcast::<Workspace>().unwrap();
@ -571,7 +572,7 @@ mod tests {
.unwrap(); .unwrap();
// Now open a file inside that workspace // Now open a file inside that workspace
open_workspace_file("/root/dir1/file1.txt", None, app_state.clone(), cx).await; open_workspace_file(path!("/root/dir1/file1.txt"), None, app_state.clone(), cx).await;
assert_eq!(cx.windows().len(), 1); assert_eq!(cx.windows().len(), 1);
workspace workspace
@ -581,7 +582,13 @@ mod tests {
.unwrap(); .unwrap();
// Now open a file inside that workspace, but tell Zed to open a new window // Now open a file inside that workspace, but tell Zed to open a new window
open_workspace_file("/root/dir1/file1.txt", Some(true), app_state.clone(), cx).await; open_workspace_file(
path!("/root/dir1/file1.txt"),
Some(true),
app_state.clone(),
cx,
)
.await;
assert_eq!(cx.windows().len(), 2); assert_eq!(cx.windows().len(), 2);
@ -599,12 +606,16 @@ mod tests {
async fn test_open_workspace_with_nonexistent_files(cx: &mut TestAppContext) { async fn test_open_workspace_with_nonexistent_files(cx: &mut TestAppContext) {
let app_state = init_test(cx); let app_state = init_test(cx);
app_state.fs.as_fake().insert_tree("/root", json!({})).await; app_state
.fs
.as_fake()
.insert_tree(path!("/root"), json!({}))
.await;
assert_eq!(cx.windows().len(), 0); assert_eq!(cx.windows().len(), 0);
// Test case 1: Open a single file that does not exist yet // Test case 1: Open a single file that does not exist yet
open_workspace_file("/root/file5.txt", None, app_state.clone(), cx).await; open_workspace_file(path!("/root/file5.txt"), None, app_state.clone(), cx).await;
assert_eq!(cx.windows().len(), 1); assert_eq!(cx.windows().len(), 1);
let workspace_1 = cx.windows()[0].downcast::<Workspace>().unwrap(); let workspace_1 = cx.windows()[0].downcast::<Workspace>().unwrap();
@ -616,7 +627,7 @@ mod tests {
// Test case 2: Open a single file that does not exist yet, // Test case 2: Open a single file that does not exist yet,
// but tell Zed to add it to the current workspace // but tell Zed to add it to the current workspace
open_workspace_file("/root/file6.txt", Some(false), app_state.clone(), cx).await; open_workspace_file(path!("/root/file6.txt"), Some(false), app_state.clone(), cx).await;
assert_eq!(cx.windows().len(), 1); assert_eq!(cx.windows().len(), 1);
workspace_1 workspace_1
@ -628,7 +639,7 @@ mod tests {
// Test case 3: Open a single file that does not exist yet, // Test case 3: Open a single file that does not exist yet,
// but tell Zed to NOT add it to the current workspace // but tell Zed to NOT add it to the current workspace
open_workspace_file("/root/file7.txt", Some(true), app_state.clone(), cx).await; open_workspace_file(path!("/root/file7.txt"), Some(true), app_state.clone(), cx).await;
assert_eq!(cx.windows().len(), 2); assert_eq!(cx.windows().len(), 2);
let workspace_2 = cx.windows()[1].downcast::<Workspace>().unwrap(); let workspace_2 = cx.windows()[1].downcast::<Workspace>().unwrap();

View file

@ -0,0 +1,22 @@
param (
[Parameter(Mandatory = $true)]
[int]$MAX_SIZE_IN_GB
)
$ErrorActionPreference = "Stop"
$PSNativeCommandUseErrorActionPreference = $true
$ProgressPreference = "SilentlyContinue"
if (-Not (Test-Path -Path "target")) {
Write-Host "target directory does not exist yet"
exit 0
}
$current_size_gb = (Get-ChildItem -Recurse -Force -File -Path "target" | Measure-Object -Property Length -Sum).Sum / 1GB
Write-Host "target directory size: ${current_size_gb}GB. max size: ${MAX_SIZE_IN_GB}GB"
if ($current_size_gb -gt $MAX_SIZE_IN_GB) {
Write-Host "Dev drive is almost full, increase the size first!"
exit 1
}

View file

@ -3,7 +3,8 @@
# The current version of the Windows runner is 10.0.20348 which does not support DevDrive option. # The current version of the Windows runner is 10.0.20348 which does not support DevDrive option.
# Ref: https://learn.microsoft.com/en-us/windows/dev-drive/ # Ref: https://learn.microsoft.com/en-us/windows/dev-drive/
$Volume = New-VHD -Path C:/zed_dev_drive.vhdx -SizeBytes 30GB | # Currently, total CI requires almost 45GB of space, here we are creating a 60GB drive.
$Volume = New-VHD -Path C:/zed_dev_drive.vhdx -SizeBytes 60GB |
Mount-VHD -Passthru | Mount-VHD -Passthru |
Initialize-Disk -Passthru | Initialize-Disk -Passthru |
New-Partition -AssignDriveLetter -UseMaximumSize | New-Partition -AssignDriveLetter -UseMaximumSize |