From 3866035b636e6e8bd564966263500723a399dc4a Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Sat, 17 Feb 2024 14:24:51 -0500 Subject: [PATCH] Add a core URI type Co-authored-by: soqb --- Cargo.lock | 1 + helix-core/Cargo.toml | 1 + helix-core/src/lib.rs | 3 ++ helix-core/src/uri.rs | 122 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 127 insertions(+) create mode 100644 helix-core/src/uri.rs diff --git a/Cargo.lock b/Cargo.lock index 2b8a25c85..69bce3f91 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1261,6 +1261,7 @@ dependencies = [ "unicode-general-category", "unicode-segmentation", "unicode-width", + "url", ] [[package]] diff --git a/helix-core/Cargo.toml b/helix-core/Cargo.toml index 0b0dd7452..562d4295e 100644 --- a/helix-core/Cargo.toml +++ b/helix-core/Cargo.toml @@ -35,6 +35,7 @@ bitflags = "2.4" ahash = "0.8.6" hashbrown = { version = "0.14.3", features = ["raw"] } dunce = "1.0" +url = "2.5.0" log = "0.4" serde = { version = "1.0", features = ["derive"] } diff --git a/helix-core/src/lib.rs b/helix-core/src/lib.rs index 94802eba9..be1fbc7bc 100644 --- a/helix-core/src/lib.rs +++ b/helix-core/src/lib.rs @@ -27,6 +27,7 @@ pub mod test; pub mod text_annotations; pub mod textobject; mod transaction; +pub mod uri; pub mod wrap; pub mod unicode { @@ -69,3 +70,5 @@ pub use diagnostic::Diagnostic; pub use line_ending::{LineEnding, NATIVE_LINE_ENDING}; pub use transaction::{Assoc, Change, ChangeSet, Deletion, Operation, Transaction}; + +pub use uri::Uri; diff --git a/helix-core/src/uri.rs b/helix-core/src/uri.rs new file mode 100644 index 000000000..4e03c58b1 --- /dev/null +++ b/helix-core/src/uri.rs @@ -0,0 +1,122 @@ +use std::path::{Path, PathBuf}; + +/// A generic pointer to a file location. +/// +/// Currently this type only supports paths to local files. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[non_exhaustive] +pub enum Uri { + File(PathBuf), +} + +impl Uri { + // This clippy allow mirrors url::Url::from_file_path + #[allow(clippy::result_unit_err)] + pub fn to_url(&self) -> Result { + match self { + Uri::File(path) => url::Url::from_file_path(path), + } + } + + pub fn as_path(&self) -> Option<&Path> { + match self { + Self::File(path) => Some(path), + } + } + + pub fn as_path_buf(self) -> Option { + match self { + Self::File(path) => Some(path), + } + } +} + +impl From for Uri { + fn from(path: PathBuf) -> Self { + Self::File(path) + } +} + +impl TryFrom for PathBuf { + type Error = (); + + fn try_from(uri: Uri) -> Result { + match uri { + Uri::File(path) => Ok(path), + } + } +} + +#[derive(Debug)] +pub struct UrlConversionError { + source: url::Url, + kind: UrlConversionErrorKind, +} + +#[derive(Debug)] +pub enum UrlConversionErrorKind { + UnsupportedScheme, + UnableToConvert, +} + +impl std::fmt::Display for UrlConversionError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self.kind { + UrlConversionErrorKind::UnsupportedScheme => { + write!(f, "unsupported scheme in URL: {}", self.source.scheme()) + } + UrlConversionErrorKind::UnableToConvert => { + write!(f, "unable to convert URL to file path: {}", self.source) + } + } + } +} + +impl std::error::Error for UrlConversionError {} + +fn convert_url_to_uri(url: &url::Url) -> Result { + if url.scheme() == "file" { + url.to_file_path() + .map(|path| Uri::File(helix_stdx::path::normalize(path))) + .map_err(|_| UrlConversionErrorKind::UnableToConvert) + } else { + Err(UrlConversionErrorKind::UnsupportedScheme) + } +} + +impl TryFrom for Uri { + type Error = UrlConversionError; + + fn try_from(url: url::Url) -> Result { + convert_url_to_uri(&url).map_err(|kind| Self::Error { source: url, kind }) + } +} + +impl TryFrom<&url::Url> for Uri { + type Error = UrlConversionError; + + fn try_from(url: &url::Url) -> Result { + convert_url_to_uri(url).map_err(|kind| Self::Error { + source: url.clone(), + kind, + }) + } +} + +#[cfg(test)] +mod test { + use super::*; + use url::Url; + + #[test] + fn unknown_scheme() { + let url = Url::parse("csharp:/metadata/foo/bar/Baz.cs").unwrap(); + assert!(matches!( + Uri::try_from(url), + Err(UrlConversionError { + kind: UrlConversionErrorKind::UnsupportedScheme, + .. + }) + )); + } +}