Detailed changes
@@ -15,7 +15,7 @@ use std::{
str::FromStr,
sync::Arc,
};
-use util::command::Stdio;
+use util::{command::Stdio, rel_path::PathExt};
use wasm_encoder::{ComponentSectionId, Encode as _, RawSection, Section as _};
use wasmparser::Parser;
@@ -108,7 +108,7 @@ impl ExtensionBuilder {
for (debug_adapter_name, meta) in &mut extension_manifest.debug_adapters {
let debug_adapter_schema_path =
- extension_dir.join(build_debug_adapter_schema_path(debug_adapter_name, meta));
+ extension_dir.join(build_debug_adapter_schema_path(debug_adapter_name, meta)?);
let debug_adapter_schema = fs::read_to_string(&debug_adapter_schema_path)
.with_context(|| {
@@ -582,8 +582,9 @@ async fn populate_defaults(
let language_dir = language_dir?;
let config_path = language_dir.join(LanguageConfig::FILE_NAME);
if fs.is_file(config_path.as_path()).await {
- let relative_language_dir =
- language_dir.strip_prefix(extension_path)?.to_path_buf();
+ let relative_language_dir = language_dir
+ .strip_prefix(extension_path)?
+ .to_rel_path_buf()?;
if !manifest.languages.contains(&relative_language_dir) {
manifest.languages.push(relative_language_dir);
}
@@ -601,7 +602,8 @@ async fn populate_defaults(
while let Some(theme_path) = theme_dir_entries.next().await {
let theme_path = theme_path?;
if theme_path.extension() == Some("json".as_ref()) {
- let relative_theme_path = theme_path.strip_prefix(extension_path)?.to_path_buf();
+ let relative_theme_path =
+ theme_path.strip_prefix(extension_path)?.to_rel_path_buf()?;
if !manifest.themes.contains(&relative_theme_path) {
manifest.themes.push(relative_theme_path);
}
@@ -619,8 +621,9 @@ async fn populate_defaults(
while let Some(icon_theme_path) = icon_theme_dir_entries.next().await {
let icon_theme_path = icon_theme_path?;
if icon_theme_path.extension() == Some("json".as_ref()) {
- let relative_icon_theme_path =
- icon_theme_path.strip_prefix(extension_path)?.to_path_buf();
+ let relative_icon_theme_path = icon_theme_path
+ .strip_prefix(extension_path)?
+ .to_rel_path_buf()?;
if !manifest.icon_themes.contains(&relative_icon_theme_path) {
manifest.icon_themes.push(relative_icon_theme_path);
}
@@ -11,6 +11,7 @@ use language::LanguageName;
use lsp::LanguageServerName;
use semver::Version;
use serde::{Deserialize, Serialize};
+use util::rel_path::{PathExt, RelPathBuf};
use crate::ExtensionCapability;
@@ -28,11 +29,11 @@ pub struct OldExtensionManifest {
pub authors: Vec<String>,
#[serde(default)]
- pub themes: BTreeMap<Arc<str>, PathBuf>,
+ pub themes: BTreeMap<Arc<str>, RelPathBuf>,
#[serde(default)]
- pub languages: BTreeMap<Arc<str>, PathBuf>,
+ pub languages: BTreeMap<Arc<str>, RelPathBuf>,
#[serde(default)]
- pub grammars: BTreeMap<Arc<str>, PathBuf>,
+ pub grammars: BTreeMap<Arc<str>, RelPathBuf>,
}
/// The schema version of the [`ExtensionManifest`].
@@ -94,11 +95,11 @@ pub struct ExtensionManifest {
pub lib: LibManifestEntry,
#[serde(default)]
- pub themes: Vec<PathBuf>,
+ pub themes: Vec<RelPathBuf>,
#[serde(default)]
- pub icon_themes: Vec<PathBuf>,
+ pub icon_themes: Vec<RelPathBuf>,
#[serde(default)]
- pub languages: Vec<PathBuf>,
+ pub languages: Vec<RelPathBuf>,
#[serde(default)]
pub grammars: BTreeMap<Arc<str>, GrammarManifestEntry>,
#[serde(default)]
@@ -195,11 +196,13 @@ impl ExtensionManifest {
pub fn build_debug_adapter_schema_path(
adapter_name: &Arc<str>,
meta: &DebugAdapterManifestEntry,
-) -> PathBuf {
- meta.schema_path.clone().unwrap_or_else(|| {
- Path::new("debug_adapter_schemas")
+) -> anyhow::Result<RelPathBuf> {
+ match &meta.schema_path {
+ Some(path) => Ok(path.clone()),
+ None => Path::new("debug_adapter_schemas")
.join(Path::new(adapter_name.as_ref()).with_extension("json"))
- })
+ .to_rel_path_buf(),
+ }
}
#[derive(Clone, Default, PartialEq, Eq, Debug, Deserialize, Serialize)]
@@ -350,7 +353,7 @@ pub struct SlashCommandManifestEntry {
#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
pub struct DebugAdapterManifestEntry {
- pub schema_path: Option<PathBuf>,
+ pub schema_path: Option<RelPathBuf>,
}
#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
@@ -442,7 +445,9 @@ fn manifest_from_old_manifest(
#[cfg(test)]
mod tests {
+ use indoc::indoc;
use pretty_assertions::assert_eq;
+ use util::rel_path::rel_path_buf;
use crate::ProcessExecCapability;
@@ -478,11 +483,11 @@ mod tests {
fn test_build_adapter_schema_path_with_schema_path() {
let adapter_name = Arc::from("my_adapter");
let entry = DebugAdapterManifestEntry {
- schema_path: Some(PathBuf::from("foo/bar")),
+ schema_path: Some(rel_path_buf("foo/bar")),
};
- let path = build_debug_adapter_schema_path(&adapter_name, &entry);
- assert_eq!(path, PathBuf::from("foo/bar"));
+ let path = build_debug_adapter_schema_path(&adapter_name, &entry).unwrap();
+ assert_eq!(path, rel_path_buf("foo/bar"));
}
#[test]
@@ -490,11 +495,8 @@ mod tests {
let adapter_name = Arc::from("my_adapter");
let entry = DebugAdapterManifestEntry { schema_path: None };
- let path = build_debug_adapter_schema_path(&adapter_name, &entry);
- assert_eq!(
- path,
- PathBuf::from("debug_adapter_schemas").join("my_adapter.json")
- );
+ let path = build_debug_adapter_schema_path(&adapter_name, &entry).unwrap();
+ assert_eq!(path, rel_path_buf("debug_adapter_schemas/my_adapter.json"));
}
#[test]
@@ -572,22 +574,37 @@ mod tests {
);
assert!(manifest.allow_exec("docker", &["ps"]).is_err()); // wrong first arg
}
+
+ #[test]
+ #[cfg(target_os = "windows")]
+ fn test_deserialize_manifest_with_windows_separators() {
+ let content = indoc! {r#"
+ id = "test-manifest"
+ name = "Test Manifest"
+ version = "0.0.1"
+ schema_version = 0
+ languages = ["foo\\bar"]
+ "#};
+ let manifest: ExtensionManifest = toml::from_str(&content).expect("manifest should parse");
+ assert_eq!(manifest.languages, vec![rel_path_buf("foo/bar")]);
+ }
+
#[test]
fn parse_manifest_with_agent_server_archive_launcher() {
- let toml_src = r#"
-id = "example.agent-server-ext"
-name = "Agent Server Example"
-version = "1.0.0"
-schema_version = 0
-
-[agent_servers.foo]
-name = "Foo Agent"
-
-[agent_servers.foo.targets.linux-x86_64]
-archive = "https://example.com/agent-linux-x64.tar.gz"
-cmd = "./agent"
-args = ["--serve"]
-"#;
+ let toml_src = indoc! {r#"
+ id = "example.agent-server-ext"
+ name = "Agent Server Example"
+ version = "1.0.0"
+ schema_version = 0
+
+ [agent_servers.foo]
+ name = "Foo Agent"
+
+ [agent_servers.foo.targets.linux-x86_64]
+ archive = "https://example.com/agent-linux-x64.tar.gz"
+ cmd = "./agent"
+ args = ["--serve"]
+ "#};
let manifest: ExtensionManifest = toml::from_str(toml_src).expect("manifest should parse");
assert_eq!(manifest.id.as_ref(), "example.agent-server-ext");
@@ -165,6 +165,7 @@ async fn copy_extension_resources(
let output_themes_dir = output_dir.join("themes");
fs::create_dir_all(&output_themes_dir)?;
for theme_path in &manifest.themes {
+ let theme_path = theme_path.as_std_path();
fs::copy(
extension_path.join(theme_path),
output_themes_dir.join(theme_path.file_name().context("invalid theme path")?),
@@ -177,6 +178,7 @@ async fn copy_extension_resources(
let output_icon_themes_dir = output_dir.join("icon_themes");
fs::create_dir_all(&output_icon_themes_dir)?;
for icon_theme_path in &manifest.icon_themes {
+ let icon_theme_path = icon_theme_path.as_std_path();
fs::copy(
extension_path.join(icon_theme_path),
output_icon_themes_dir.join(
@@ -224,6 +226,7 @@ async fn copy_extension_resources(
let output_languages_dir = output_dir.join("languages");
fs::create_dir_all(&output_languages_dir)?;
for language_path in &manifest.languages {
+ let language_path = language_path.as_std_path();
copy_recursive(
fs.as_ref(),
&extension_path.join(language_path),
@@ -243,14 +246,11 @@ async fn copy_extension_resources(
if !manifest.debug_adapters.is_empty() {
for (debug_adapter, entry) in &manifest.debug_adapters {
- let schema_path = entry.schema_path.clone().unwrap_or_else(|| {
- PathBuf::from("debug_adapter_schemas".to_owned())
- .join(debug_adapter.as_ref())
- .with_extension("json")
- });
+ let schema_path = extension::build_debug_adapter_schema_path(debug_adapter, entry)?;
let parent = schema_path
.parent()
.with_context(|| format!("invalid empty schema path for {debug_adapter}"))?;
+ let schema_path = schema_path.as_std_path();
fs::create_dir_all(output_dir.join(parent))?;
copy_recursive(
fs.as_ref(),
@@ -265,7 +265,7 @@ async fn copy_extension_resources(
.with_context(|| {
format!(
"failed to copy debug adapter schema '{}'",
- schema_path.display()
+ schema_path.display(),
)
})?;
}
@@ -56,7 +56,7 @@ use std::{
};
use task::TaskTemplates;
use url::Url;
-use util::{ResultExt, paths::RemotePathBuf};
+use util::{ResultExt, paths::RemotePathBuf, rel_path::PathExt};
use wasm_host::{
WasmExtension, WasmHost,
wit::{is_supported_wasm_api_version, wasm_api_version_range},
@@ -1244,13 +1244,16 @@ impl ExtensionStore {
}));
themes_to_add.extend(extension.manifest.themes.iter().map(|theme_path| {
let mut path = self.installed_dir.clone();
- path.extend([Path::new(extension_id.as_ref()), theme_path.as_path()]);
+ path.extend([Path::new(extension_id.as_ref()), theme_path.as_std_path()]);
path
}));
icon_themes_to_add.extend(extension.manifest.icon_themes.iter().map(
|icon_theme_path| {
let mut path = self.installed_dir.clone();
- path.extend([Path::new(extension_id.as_ref()), icon_theme_path.as_path()]);
+ path.extend([
+ Path::new(extension_id.as_ref()),
+ icon_theme_path.as_std_path(),
+ ]);
let mut icons_root_path = self.installed_dir.clone();
icons_root_path.extend([Path::new(extension_id.as_ref())]);
@@ -1560,7 +1563,7 @@ impl ExtensionStore {
})?;
let config = ::toml::from_str::<LanguageConfig>(&config)?;
- let relative_path = relative_path.to_path_buf();
+ let relative_path = relative_path.to_rel_path_buf()?;
if !extension_manifest.languages.contains(&relative_path) {
extension_manifest.languages.push(relative_path.clone());
}
@@ -1569,7 +1572,7 @@ impl ExtensionStore {
config.name.clone(),
ExtensionIndexLanguageEntry {
extension: extension_id.clone(),
- path: relative_path,
+ path: relative_path.as_std_path().to_path_buf(),
matcher: config.matcher,
hidden: config.hidden,
grammar: config.grammar,
@@ -1593,7 +1596,7 @@ impl ExtensionStore {
continue;
};
- let relative_path = relative_path.to_path_buf();
+ let relative_path = relative_path.to_rel_path_buf()?;
if !extension_manifest.themes.contains(&relative_path) {
extension_manifest.themes.push(relative_path.clone());
}
@@ -1603,7 +1606,7 @@ impl ExtensionStore {
theme_name.into(),
ExtensionIndexThemeEntry {
extension: extension_id.clone(),
- path: relative_path.clone(),
+ path: relative_path.as_std_path().to_path_buf(),
},
);
}
@@ -1625,7 +1628,7 @@ impl ExtensionStore {
continue;
};
- let relative_path = relative_path.to_path_buf();
+ let relative_path = relative_path.to_rel_path_buf()?;
if !extension_manifest.icon_themes.contains(&relative_path) {
extension_manifest.icon_themes.push(relative_path.clone());
}
@@ -1635,7 +1638,7 @@ impl ExtensionStore {
icon_theme_name.into(),
ExtensionIndexIconThemeEntry {
extension: extension_id.clone(),
- path: relative_path.clone(),
+ path: relative_path.as_std_path().to_path_buf(),
},
);
}
@@ -1721,15 +1724,15 @@ impl ExtensionStore {
}
for (adapter_name, meta) in loaded_extension.manifest.debug_adapters.iter() {
- let schema_path = &extension::build_debug_adapter_schema_path(adapter_name, meta);
+ let schema_path = extension::build_debug_adapter_schema_path(adapter_name, meta)?;
- if fs.is_file(&src_dir.join(schema_path)).await {
+ if fs.is_file(&src_dir.join(&schema_path)).await {
if let Some(parent) = schema_path.parent() {
fs.create_dir(&tmp_dir.join(parent)).await?
}
fs.copy_file(
- &src_dir.join(schema_path),
- &tmp_dir.join(schema_path),
+ &src_dir.join(&schema_path),
+ &tmp_dir.join(&schema_path),
fs::CopyOptions::default(),
)
.await?
@@ -26,7 +26,7 @@ use std::{
sync::Arc,
};
use theme::ThemeRegistry;
-use util::test::TempTree;
+use util::{rel_path::rel_path_buf, test::TempTree};
#[cfg(test)]
#[ctor::ctor]
@@ -150,7 +150,10 @@ async fn test_extension_store(cx: &mut TestAppContext) {
themes: Default::default(),
icon_themes: Vec::new(),
lib: Default::default(),
- languages: vec!["languages/erb".into(), "languages/ruby".into()],
+ languages: vec![
+ rel_path_buf("languages/erb"),
+ rel_path_buf("languages/ruby"),
+ ],
grammars: [
("embedded_template".into(), GrammarManifestEntry::default()),
("ruby".into(), GrammarManifestEntry::default()),
@@ -182,8 +185,8 @@ async fn test_extension_store(cx: &mut TestAppContext) {
authors: vec![],
repository: None,
themes: vec![
- "themes/monokai-pro.json".into(),
- "themes/monokai.json".into(),
+ rel_path_buf("themes/monokai-pro.json"),
+ rel_path_buf("themes/monokai.json"),
],
icon_themes: Vec::new(),
lib: Default::default(),
@@ -367,7 +370,7 @@ async fn test_extension_store(cx: &mut TestAppContext) {
description: None,
authors: vec![],
repository: None,
- themes: vec!["themes/gruvbox.json".into()],
+ themes: vec![rel_path_buf("themes/gruvbox.json")],
icon_themes: Vec::new(),
lib: Default::default(),
languages: Default::default(),
@@ -194,7 +194,7 @@ impl HeadlessExtensionStore {
}
for (debug_adapter, meta) in &manifest.debug_adapters {
- let schema_path = extension::build_debug_adapter_schema_path(debug_adapter, meta);
+ let schema_path = extension::build_debug_adapter_schema_path(debug_adapter, meta)?;
this.update(cx, |this, _cx| {
this.proxy.register_debug_adapter(
@@ -27,7 +27,7 @@ pub struct RelPath(str);
/// relative and normalized.
///
/// This type is to [`RelPath`] as [`std::path::PathBuf`] is to [`std::path::Path`]
-#[derive(PartialEq, Eq, Clone, Serialize, Deserialize)]
+#[derive(PartialEq, Eq, Clone, Ord, PartialOrd, Serialize)]
pub struct RelPathBuf(String);
impl RelPath {
@@ -333,12 +333,36 @@ impl RelPathBuf {
}
}
+impl<'de> Deserialize<'de> for RelPathBuf {
+ fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
+ where
+ D: serde::Deserializer<'de>,
+ {
+ let path = String::deserialize(deserializer)?;
+ let rel_path =
+ RelPath::new(Path::new(&path), PathStyle::local()).map_err(serde::de::Error::custom)?;
+ Ok(rel_path.into_owned())
+ }
+}
+
impl Into<Arc<RelPath>> for RelPathBuf {
fn into(self) -> Arc<RelPath> {
Arc::from(self.as_rel_path())
}
}
+impl AsRef<Path> for RelPathBuf {
+ fn as_ref(&self) -> &Path {
+ self.as_std_path()
+ }
+}
+
+impl AsRef<Path> for RelPath {
+ fn as_ref(&self) -> &Path {
+ self.as_std_path()
+ }
+}
+
impl AsRef<RelPath> for RelPathBuf {
fn as_ref(&self) -> &RelPath {
self.as_rel_path()
@@ -378,12 +402,28 @@ pub fn rel_path(path: &str) -> &RelPath {
RelPath::unix(path).unwrap()
}
+#[cfg(any(test, feature = "test-support"))]
+#[track_caller]
+pub fn rel_path_buf(path: &str) -> RelPathBuf {
+ RelPath::unix(path).unwrap().to_rel_path_buf()
+}
+
impl PartialEq<str> for RelPath {
fn eq(&self, other: &str) -> bool {
self.0 == *other
}
}
+pub trait PathExt {
+ fn to_rel_path_buf(&self) -> Result<RelPathBuf>;
+}
+
+impl<T: AsRef<Path> + ?Sized> PathExt for T {
+ fn to_rel_path_buf(&self) -> Result<RelPathBuf> {
+ Ok(RelPath::new(self.as_ref(), PathStyle::local())?.into_owned())
+ }
+}
+
#[derive(Default)]
pub struct RelPathComponents<'a>(&'a str);