Skip to content

Commit

Permalink
Create helix-stdx crate for stdlib extensions
Browse files Browse the repository at this point in the history
helix-stdx is meant to carry extensions to the stdlib or low-level
dependencies that are useful in all other crates. This commit starts
with all of the path functions from helix-core and the CWD tracking that
lived in helix-loader.

The CWD tracking in helix-loader was previously unable to call the
canonicalization functions in helix-core. Switching to our custom
canonicalization code should make no noticeable difference though
since `std::env::current_dir` returns a canonicalized path with
symlinks resolved (at least on unix).
  • Loading branch information
the-mikedavis authored and archseer committed Jan 18, 2024
1 parent af8e524 commit 1f916e6
Show file tree
Hide file tree
Showing 27 changed files with 163 additions and 111 deletions.
14 changes: 13 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ members = [
"helix-loader",
"helix-vcs",
"helix-parsec",
"helix-stdx",
"xtask",
]

Expand Down
2 changes: 1 addition & 1 deletion helix-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ unicode-lines = ["ropey/unicode_lines"]
integration = []

[dependencies]
helix-stdx = { path = "../helix-stdx" }
helix-loader = { path = "../helix-loader" }

ropey = { version = "1.6.1", default-features = false, features = ["simd"] }
Expand Down Expand Up @@ -55,4 +56,3 @@ parking_lot = "0.12"
[dev-dependencies]
quickcheck = { version = "1", default-features = false }
indoc = "2.0.4"
tempfile = "3.9"
1 change: 0 additions & 1 deletion helix-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ pub mod macros;
pub mod match_brackets;
pub mod movement;
pub mod object;
pub mod path;
mod position;
pub mod search;
pub mod selection;
Expand Down
44 changes: 3 additions & 41 deletions helix-loader/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,46 +1,20 @@
pub mod config;
pub mod grammar;

use helix_stdx::{env::current_working_dir, path};

use etcetera::base_strategy::{choose_base_strategy, BaseStrategy};
use std::path::{Path, PathBuf};
use std::sync::RwLock;

pub const VERSION_AND_GIT_HASH: &str = env!("VERSION_AND_GIT_HASH");

static CWD: RwLock<Option<PathBuf>> = RwLock::new(None);

static RUNTIME_DIRS: once_cell::sync::Lazy<Vec<PathBuf>> =
once_cell::sync::Lazy::new(prioritize_runtime_dirs);

static CONFIG_FILE: once_cell::sync::OnceCell<PathBuf> = once_cell::sync::OnceCell::new();

static LOG_FILE: once_cell::sync::OnceCell<PathBuf> = once_cell::sync::OnceCell::new();

// Get the current working directory.
// This information is managed internally as the call to std::env::current_dir
// might fail if the cwd has been deleted.
pub fn current_working_dir() -> PathBuf {
if let Some(path) = &*CWD.read().unwrap() {
return path.clone();
}

let path = std::env::current_dir()
.and_then(dunce::canonicalize)
.expect("Couldn't determine current working directory");
let mut cwd = CWD.write().unwrap();
*cwd = Some(path.clone());

path
}

pub fn set_current_working_dir(path: impl AsRef<Path>) -> std::io::Result<()> {
let path = dunce::canonicalize(path)?;
std::env::set_current_dir(&path)?;
let mut cwd = CWD.write().unwrap();
*cwd = Some(path);
Ok(())
}

pub fn initialize_config_file(specified_file: Option<PathBuf>) {
let config_file = specified_file.unwrap_or_else(default_config_file);
ensure_parent_dir(&config_file);
Expand Down Expand Up @@ -280,21 +254,9 @@ fn ensure_parent_dir(path: &Path) {
mod merge_toml_tests {
use std::str;

use super::{current_working_dir, merge_toml_values, set_current_working_dir};
use super::merge_toml_values;
use toml::Value;

#[test]
fn current_dir_is_set() {
let new_path = dunce::canonicalize(std::env::temp_dir()).unwrap();
let cwd = current_working_dir();
assert_ne!(cwd, new_path);

set_current_working_dir(&new_path).expect("Couldn't set new path");

let cwd = current_working_dir();
assert_eq!(cwd, new_path);
}

#[test]
fn language_toml_map_merges() {
const USER: &str = r#"
Expand Down
1 change: 1 addition & 0 deletions helix-lsp/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ homepage.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
helix-stdx = { path = "../helix-stdx" }
helix-core = { path = "../helix-core" }
helix-loader = { path = "../helix-loader" }
helix-parsec = { path = "../helix-parsec" }
Expand Down
7 changes: 4 additions & 3 deletions helix-lsp/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ use crate::{
Call, Error, OffsetEncoding, Result,
};

use helix_core::{find_workspace, path, syntax::LanguageServerFeature, ChangeSet, Rope};
use helix_core::{find_workspace, syntax::LanguageServerFeature, ChangeSet, Rope};
use helix_loader::{self, VERSION_AND_GIT_HASH};
use helix_stdx::path;
use lsp::{
notification::DidChangeWorkspaceFolders, CodeActionCapabilityResolveSupport,
DidChangeWorkspaceFoldersParams, OneOf, PositionEncodingKind, WorkspaceFolder,
Expand Down Expand Up @@ -68,7 +69,7 @@ impl Client {
may_support_workspace: bool,
) -> bool {
let (workspace, workspace_is_cwd) = find_workspace();
let workspace = path::get_normalized_path(&workspace);
let workspace = path::normalize(workspace);
let root = find_lsp_workspace(
doc_path
.and_then(|x| x.parent().and_then(|x| x.to_str()))
Expand Down Expand Up @@ -204,7 +205,7 @@ impl Client {
let (server_rx, server_tx, initialize_notify) =
Transport::start(reader, writer, stderr, id, name.clone());
let (workspace, workspace_is_cwd) = find_workspace();
let workspace = path::get_normalized_path(&workspace);
let workspace = path::normalize(workspace);
let root = find_lsp_workspace(
doc_path
.and_then(|x| x.parent().and_then(|x| x.to_str()))
Expand Down
12 changes: 6 additions & 6 deletions helix-lsp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ pub use lsp::{Position, Url};
pub use lsp_types as lsp;

use futures_util::stream::select_all::SelectAll;
use helix_core::{
path,
syntax::{LanguageConfiguration, LanguageServerConfiguration, LanguageServerFeatures},
use helix_core::syntax::{
LanguageConfiguration, LanguageServerConfiguration, LanguageServerFeatures,
};
use helix_stdx::path;
use tokio::sync::mpsc::UnboundedReceiver;

use std::{
Expand Down Expand Up @@ -958,10 +958,10 @@ pub fn find_lsp_workspace(
let mut file = if file.is_absolute() {
file.to_path_buf()
} else {
let current_dir = helix_loader::current_working_dir();
let current_dir = helix_stdx::env::current_working_dir();
current_dir.join(file)
};
file = path::get_normalized_path(&file);
file = path::normalize(&file);

if !file.starts_with(workspace) {
return None;
Expand All @@ -978,7 +978,7 @@ pub fn find_lsp_workspace(

if root_dirs
.iter()
.any(|root_dir| path::get_normalized_path(&workspace.join(root_dir)) == ancestor)
.any(|root_dir| path::normalize(workspace.join(root_dir)) == ancestor)
{
// if the worskapce is the cwd do not search any higher for workspaces
// but specify
Expand Down
19 changes: 19 additions & 0 deletions helix-stdx/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[package]
name = "helix-stdx"
description = "Standard library extensions"
include = ["src/**/*", "README.md"]
version.workspace = true
authors.workspace = true
edition.workspace = true
license.workspace = true
rust-version.workspace = true
categories.workspace = true
repository.workspace = true
homepage.workspace = true

[dependencies]
dunce = "1.0"
etcetera = "0.8"

[dev-dependencies]
tempfile = "3.9"
48 changes: 48 additions & 0 deletions helix-stdx/src/env.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
use std::{
path::{Path, PathBuf},
sync::RwLock,
};

static CWD: RwLock<Option<PathBuf>> = RwLock::new(None);

// Get the current working directory.
// This information is managed internally as the call to std::env::current_dir
// might fail if the cwd has been deleted.
pub fn current_working_dir() -> PathBuf {
if let Some(path) = &*CWD.read().unwrap() {
return path.clone();
}

let path = std::env::current_dir()
.map(crate::path::normalize)
.expect("Couldn't determine current working directory");
let mut cwd = CWD.write().unwrap();
*cwd = Some(path.clone());

path
}

pub fn set_current_working_dir(path: impl AsRef<Path>) -> std::io::Result<()> {
let path = crate::path::canonicalize(path);
std::env::set_current_dir(&path)?;
let mut cwd = CWD.write().unwrap();
*cwd = Some(path);
Ok(())
}

#[cfg(test)]
mod tests {
use super::{current_working_dir, set_current_working_dir};

#[test]
fn current_dir_is_set() {
let new_path = dunce::canonicalize(std::env::temp_dir()).unwrap();
let cwd = current_working_dir();
assert_ne!(cwd, new_path);

set_current_working_dir(&new_path).expect("Couldn't set new path");

let cwd = current_working_dir();
assert_eq!(cwd, new_path);
}
}
2 changes: 2 additions & 0 deletions helix-stdx/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod env;
pub mod path;
34 changes: 19 additions & 15 deletions helix-core/src/path.rs → helix-stdx/src/path.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use etcetera::home_dir;
pub use etcetera::home_dir;

use std::path::{Component, Path, PathBuf};

use crate::env::current_working_dir;

/// Replaces users home directory from `path` with tilde `~` if the directory
/// is available, otherwise returns the path unchanged.
pub fn fold_home_dir(path: &Path) -> PathBuf {
Expand All @@ -16,7 +19,8 @@ pub fn fold_home_dir(path: &Path) -> PathBuf {
/// Expands tilde `~` into users home directory if available, otherwise returns the path
/// unchanged. The tilde will only be expanded when present as the first component of the path
/// and only slash follows it.
pub fn expand_tilde(path: &Path) -> PathBuf {
pub fn expand_tilde(path: impl AsRef<Path>) -> PathBuf {
let path = path.as_ref();
let mut components = path.components().peekable();
if let Some(Component::Normal(c)) = components.peek() {
if c == &"~" {
Expand All @@ -33,8 +37,8 @@ pub fn expand_tilde(path: &Path) -> PathBuf {
/// Normalize a path without resolving symlinks.
// Strategy: start from the first component and move up. Cannonicalize previous path,
// join component, cannonicalize new path, strip prefix and join to the final result.
pub fn get_normalized_path(path: &Path) -> PathBuf {
let mut components = path.components().peekable();
pub fn normalize(path: impl AsRef<Path>) -> PathBuf {
let mut components = path.as_ref().components().peekable();
let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
components.next();
PathBuf::from(c.as_os_str())
Expand Down Expand Up @@ -104,22 +108,22 @@ pub fn get_normalized_path(path: &Path) -> PathBuf {
///
/// This function is used instead of [`std::fs::canonicalize`] because we don't want to verify
/// here if the path exists, just normalize it's components.
pub fn get_canonicalized_path(path: &Path) -> PathBuf {
pub fn canonicalize(path: impl AsRef<Path>) -> PathBuf {
let path = expand_tilde(path);
let path = if path.is_relative() {
helix_loader::current_working_dir().join(path)
current_working_dir().join(path)
} else {
path
};

get_normalized_path(path.as_path())
normalize(path)
}

pub fn get_relative_path(path: &Path) -> PathBuf {
let path = PathBuf::from(path);
pub fn get_relative_path(path: impl AsRef<Path>) -> PathBuf {
let path = PathBuf::from(path.as_ref());
let path = if path.is_absolute() {
let cwdir = get_normalized_path(&helix_loader::current_working_dir());
get_normalized_path(&path)
let cwdir = normalize(current_working_dir());
normalize(&path)
.strip_prefix(cwdir)
.map(PathBuf::from)
.unwrap_or(path)
Expand All @@ -135,8 +139,8 @@ pub fn get_relative_path(path: &Path) -> PathBuf {
/// Also strip the current working directory from the beginning of the path.
/// Note that this function does not check if the truncated path is unambiguous.
///
/// ```
/// use helix_core::path::get_truncated_path;
/// ```
/// use helix_stdx::path::get_truncated_path;
/// use std::path::Path;
///
/// assert_eq!(
Expand All @@ -158,8 +162,8 @@ pub fn get_relative_path(path: &Path) -> PathBuf {
/// assert_eq!(get_truncated_path("").as_path(), Path::new(""));
/// ```
///
pub fn get_truncated_path<P: AsRef<Path>>(path: P) -> PathBuf {
let cwd = helix_loader::current_working_dir();
pub fn get_truncated_path(path: impl AsRef<Path>) -> PathBuf {
let cwd = current_working_dir();
let path = path
.as_ref()
.strip_prefix(cwd)
Expand Down
Loading

0 comments on commit 1f916e6

Please sign in to comment.