diff --git a/Cargo.lock b/Cargo.lock index 5e9b49e1d95e..00fea5107566 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1467,11 +1467,14 @@ name = "helix-loader" version = "25.7.1" dependencies = [ "anyhow", + "arc-swap", "cc", "etcetera", + "globset", "helix-stdx", "log", "once_cell", + "parking_lot", "serde", "tempfile", "threadpool", @@ -1567,7 +1570,6 @@ dependencies = [ "nucleo", "once_cell", "open", - "parking_lot", "pulldown-cmark", "same-file", "serde", diff --git a/book/src/workspace-trust.md b/book/src/workspace-trust.md index 1975b6e94a93..b48bcb1f5bd6 100644 --- a/book/src/workspace-trust.md +++ b/book/src/workspace-trust.md @@ -1,23 +1,63 @@ # Workspace trust -Helix has a number of potentially dangerous features, namely LSP and ability to use local to workspace configurations. Those features can lead to unexpected code execution. To protect against code execution in dangerous contexts, Helix has a workspace trust protection, which will prevent these potentially dangerous features from running automatically. +Helix has a number of potentially dangerous features, namely LSP and ability to use workspace-local configuration files. Those features can lead to unexpected code execution. To protect against code execution in dangerous contexts, Helix has a workspace trust protection, which will prevent these potentially dangerous features from running automatically. Helix will not trust any workspace by default. -By default, it will prompt about trust when you open new file in a workspace where you didn't make a decision about trust yet. +By default, it will prompt about trust when you open a new file in a workspace where you didn't make a decision about trust yet. -If you decide not to trust a workspace and don't want to be prompted about trust every time you start a new session in it, you can exclude the workspace by choosing `Never` option in trust selection window. +If you decide not to trust a workspace and don't want to be prompted about trust each session, you can exclude the workspace by choosing `Never` option in trust selection window. You can always make current workspace trusted by running `:workspace-trust` command, and untrust it with `:workspace-untrust`. -Lists of trusted and excluded workspaces, delimited by newline characters, are stored in `~/.local/share/helix/trusted_workspaces` and `~/.local/share/helix/excluded_workspaces` correspondingly. +The list of trusted and permanently untrusted workspaces, delimited by newline characters, are stored in `~/.local/share/helix/trusted_workspaces` and `~/.local/share/helix/excluded_workspaces` respectively. # Configuration -You can return to the old behaviour of loading every local `.helix/config.toml` and `.helix/languages.toml` and starting LSP's without an explicit permission by setting following option: +You can return to the old behaviour to accept the risk of loading every local `.helix/config.toml` and `.helix/languages.toml` and starting LSPs without an explicit permission by setting the following option: ```toml -[editor] -insecure = true +[editor.trust] +paths = [ "**" ] ``` + +In addition to trusting any workspaces with the wildcard glob `**`, it is possible to configure trust fine-grained with a `.gitignore` like syntax. E.g. consider the following: + +```toml +[editor.trust] +paths = [ + "~/repos/helix", + "~/repos/foo/*", + "~/repos/bar/**", + "!~/repos/bar/untrusted" +] +``` + +This would result in the following trust levels assuming the home directory `/home/user`: + +| Path | Decision | +|:----------------------------------------------- |:------------------- | +| `/home/user/foobar` | undecided | +| `/home/user/repos/helix` | trusted | +| `/home/other/repos/helix` | undecided | +| `/home/user/repos/helix/branch_a` | undecided | +| `/home/user/repos/foo` | undecided | +| `/home/user/repos/foo/branch_a` | trusted | +| `/home/user/repos/foo/remote_a/branch_a` | undecided | +| `/home/user/repos/bar/branch_a` | trusted | +| `/home/user/repos/bar/remote_a/branch_a` | trusted | +| `/home/user/repos/bar/untrusted` | untrusted | + +Specifically, the paths are processed one entry at a time by expanding a leading `~/` to the user's home directory, expanding a `*` to any single path segment, and expanding `**` to any number of path segments. An entry prefixed with `!` is a negated entry, instead of granting trust to a matching workspace it will deny it. After processing the full list, the most recently matched entry "wins". If no entry matched (e.g. because the default configuration with an empty list of `paths` was used), the user will be prompted as described in the beginning. + +A secure configuration that will never prompt would be: + +```toml +paths = [ + "!**", + "~/repos/helix", + ... +``` + +Above configuration will only grant trust to `~/repos/helix` and deny trust to any other path. diff --git a/helix-core/src/config.rs b/helix-core/src/config.rs index 79bdcad1d980..7e767aadd8fc 100644 --- a/helix-core/src/config.rs +++ b/helix-core/src/config.rs @@ -37,13 +37,17 @@ impl std::fmt::Display for LanguageLoaderError { impl std::error::Error for LanguageLoaderError {} /// Language configuration based on user configured languages.toml. -pub fn user_lang_config(insecure: bool) -> Result { - helix_loader::config::user_lang_config(insecure)?.try_into() +pub fn user_lang_config( + config: &helix_loader::workspace_trust::Config, +) -> Result { + helix_loader::config::user_lang_config(config)?.try_into() } /// Language configuration loader based on user configured languages.toml. -pub fn user_lang_loader(insecure: bool) -> Result { - let config_val = helix_loader::config::user_lang_config(insecure) +pub fn user_lang_loader( + config: &helix_loader::workspace_trust::Config, +) -> Result { + let config_val = helix_loader::config::user_lang_config(config) .map_err(LanguageLoaderError::DeserializeError)?; let config = config_val.clone().try_into().map_err(|e| { if let Some(languages) = config_val.get("language").and_then(|v| v.as_array()) { diff --git a/helix-loader/Cargo.toml b/helix-loader/Cargo.toml index 88267868cde3..62824c12a730 100644 --- a/helix-loader/Cargo.toml +++ b/helix-loader/Cargo.toml @@ -9,6 +9,7 @@ rust-version.workspace = true categories.workspace = true repository.workspace = true homepage.workspace = true +globset.workspace = true [[bin]] name = "hx-loader" @@ -23,6 +24,7 @@ toml.workspace = true etcetera.workspace = true once_cell = "1.21" log = "0.4" +parking_lot.workspace = true # TODO: these two should be on !wasm32 only @@ -32,3 +34,5 @@ threadpool = { version = "1.0" } tempfile.workspace = true tree-house.workspace = true +globset.workspace = true +arc-swap.workspace = true diff --git a/helix-loader/src/config.rs b/helix-loader/src/config.rs index 04bb55bf49ac..4490d06a23d9 100644 --- a/helix-loader/src/config.rs +++ b/helix-loader/src/config.rs @@ -1,6 +1,6 @@ use std::str::from_utf8; -use crate::workspace_trust::{quick_query_workspace, TrustStatus}; +use crate::workspace_trust::{self, quick_query_workspace, TrustStatus}; /// Default built-in languages.toml. pub fn default_lang_config() -> toml::Value { @@ -10,11 +10,11 @@ pub fn default_lang_config() -> toml::Value { } /// User configured languages.toml file, merged with the default config. -pub fn user_lang_config(insecure: bool) -> Result { +pub fn user_lang_config(config: &workspace_trust::Config) -> Result { let global_config = crate::lang_config_file(); let workspace_config = crate::workspace_lang_config_file(); - let files = if let TrustStatus::Trusted = quick_query_workspace(insecure) { + let files = if let TrustStatus::Trusted = quick_query_workspace(config) { vec![global_config, workspace_config] } else { vec![global_config] diff --git a/helix-loader/src/grammar.rs b/helix-loader/src/grammar.rs index 3574e589df46..4b5c40e41d4d 100644 --- a/helix-loader/src/grammar.rs +++ b/helix-loader/src/grammar.rs @@ -195,9 +195,10 @@ pub fn build_grammars(target: Option) -> Result<()> { // merged. The `grammar_selection` key of the config is then used to filter // down all grammars into a subset of the user's choosing. fn get_grammar_configs() -> Result> { - let config: Configuration = crate::config::user_lang_config(false) - .context("Could not parse languages.toml")? - .try_into()?; + let config: Configuration = + crate::config::user_lang_config(&crate::workspace_trust::Config::default()) + .context("Could not parse languages.toml")? + .try_into()?; let grammars = match config.grammar_selection { Some(GrammarSelection::Only { only: selections }) => config @@ -217,9 +218,10 @@ fn get_grammar_configs() -> Result> { } pub fn get_grammar_names() -> Result>> { - let config: Configuration = crate::config::user_lang_config(false) - .context("Could not parse languages.toml")? - .try_into()?; + let config: Configuration = + crate::config::user_lang_config(&crate::workspace_trust::Config::default()) + .context("Could not parse languages.toml")? + .try_into()?; let grammars = match config.grammar_selection { Some(GrammarSelection::Only { only: selections }) => Some(selections), diff --git a/helix-loader/src/workspace_trust.rs b/helix-loader/src/workspace_trust.rs index c3b023cb5ae8..370998376a42 100644 --- a/helix-loader/src/workspace_trust.rs +++ b/helix-loader/src/workspace_trust.rs @@ -1,10 +1,17 @@ +use crate::{data_dir, workspace_exclude_file, workspace_trust_file}; +use globset::GlobBuilder; +use once_cell::sync::Lazy; +use parking_lot::Mutex; +use serde::{Deserialize, Serialize}; use std::{ - collections::HashSet, - fs, + collections::{HashMap, HashSet}, + env, fs, + io::{BufRead, BufReader}, path::{Path, PathBuf}, }; -use crate::{data_dir, workspace_exclude_file, workspace_trust_file}; +static WORKSPACE_TRUST_CACHE: Lazy>> = + Lazy::new(|| Mutex::new(HashMap::new())); pub struct WorkspaceTrust { trusted: HashSet, @@ -17,6 +24,12 @@ pub enum TrustStatus { Trusted, } +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] +#[serde(rename_all = "kebab-case", default, deny_unknown_fields)] +pub struct Config { + pub paths: Vec, +} + impl WorkspaceTrust { /// Loads `WorkspaceTrust`. /// @@ -105,15 +118,21 @@ impl WorkspaceTrust { /// Mark current workspace trusted pub fn trust_workspace(&mut self) { let workspace = crate::find_workspace().0; - self.trusted.insert(workspace); + self.trusted.insert(workspace.clone()); self.write_trust_to_file(); + let mut cache = WORKSPACE_TRUST_CACHE.lock(); + cache.insert(workspace, TrustUntrustStatus::AllowAlways); } /// Remove trusted mark from current workspace pub fn untrust_workspace(&mut self) { let workspace = crate::find_workspace().0; - self.trusted.remove(&workspace); - self.write_trust_to_file(); + if self.trusted.remove(&workspace) { + // only update the file if there was a change + self.write_trust_to_file(); + } + let mut cache = WORKSPACE_TRUST_CACHE.lock(); + cache.insert(workspace, TrustUntrustStatus::DenyOnce); } /// Mark current workspace excluded. @@ -128,10 +147,13 @@ impl WorkspaceTrust { } else { log::error!("Called untrust_workspace_permanent() when self.untrusted is None"); } + let workspace = crate::find_workspace().0; + let mut cache = WORKSPACE_TRUST_CACHE.lock(); + cache.insert(workspace, TrustUntrustStatus::DenyAlways); } } -#[derive(Default, Clone, Copy, Debug)] +#[derive(Default, Clone, Copy, Debug, PartialEq)] pub enum TrustUntrustStatus { DenyAlways, #[default] @@ -139,54 +161,223 @@ pub enum TrustUntrustStatus { AllowAlways, } -pub fn quick_query_workspace(insecure: bool) -> TrustStatus { - if insecure { - return TrustStatus::Trusted; - } +fn is_path_in_file(needle: &Path, haystack_path: &Path) -> bool { + let file = match fs::File::open(haystack_path) { + Ok(file) => file, + Err(err) if err.kind() == std::io::ErrorKind::NotFound => { + log::debug!("workspace trust file {haystack_path:?} does not exist"); + return false; + } + Err(err) => { + log::error!("workspace trust file {haystack_path:?} couldn't be read: {err:?}"); + return false; + } + }; - let workspace = crate::find_workspace().0; - match fs::read_to_string(workspace_trust_file()) { - Ok(workspace_trust_file) => { - for line in workspace_trust_file.split('\n') { - if Path::new(line) == workspace { - return TrustStatus::Trusted; + for (lineno, line) in BufReader::new(file).lines().enumerate() { + match line { + Ok(line) => { + if PathBuf::from(line) == needle { + return true; } } + Err(err) => { + log::error!( + "workspace trust file {haystack_path:?}:{} couldn't be read: {err:?}", + lineno + 1 + ) + } } - Err(err) if err.kind() == std::io::ErrorKind::NotFound => (), - Err(err) => log::error!("workspace file couldn't be read: {err:?}"), - }; - TrustStatus::Untrusted + } + + false } -pub fn quick_query_workspace_with_explicit_untrust(insecure: bool) -> TrustUntrustStatus { - if insecure { - return TrustUntrustStatus::AllowAlways; +pub fn quick_query_workspace(config: &Config) -> TrustStatus { + match quick_query_workspace_with_explicit_untrust(config) { + Some(TrustUntrustStatus::AllowAlways) => TrustStatus::Trusted, + _ => TrustStatus::Untrusted, } +} - let workspace = crate::find_workspace().0; - match fs::read_to_string(workspace_trust_file()) { - Ok(workspace_trust_file) => { - for line in workspace_trust_file.split('\n') { - if Path::new(line) == workspace { - return TrustUntrustStatus::AllowAlways; - } - } +fn is_path_matching_glob(path: &Path, glob: &str, home: &Path) -> bool { + let (path, glob) = if let Some(glob_remain) = glob.strip_prefix("~/") { + match path.strip_prefix(home) { + Ok(path_remain) => (path_remain, glob_remain), + Err(_) => return false, } - Err(err) if err.kind() == std::io::ErrorKind::NotFound => (), - Err(err) => log::error!("workspace_trust file couldn't be read: {err:?}"), + } else { + (path, glob) }; - match fs::read_to_string(workspace_exclude_file()) { - Ok(workspace_untrust_file) => { - for line in workspace_untrust_file.split('\n') { - if Path::new(line) == workspace { - return TrustUntrustStatus::DenyAlways; - } + match GlobBuilder::new(glob).literal_separator(true).build() { + Ok(glob) => glob.compile_matcher().is_match(path), + _ => false, + } +} + +#[test] +fn is_path_matching_glob_test() { + let test_cases = vec![ + ( + PathBuf::from("/home/user/repo"), + "/home/user/repo", + PathBuf::from("/home/user"), + true, + ), + ( + PathBuf::from("/home/user/repo"), + "~/repo", + PathBuf::from("/home/user"), + true, + ), + ( + PathBuf::from("/home/user2/repo"), + "~/repo", + PathBuf::from("/home/user"), + false, + ), + ]; + + for (path, glob, home, result) in test_cases { + assert_eq!( + is_path_matching_glob(&path, glob, &home), + result, + "is_path_matching_glob({path:?}, \"{glob}\", {home:?}) != {result}" + ); + } +} + +fn trust_from_globs( + workspace: &Path, + home: &Path, + globs: &Vec, +) -> Option { + let mut result: Option = None; + for glob in globs { + if let Some(glob) = glob.strip_prefix("!") { + if is_path_matching_glob(workspace, glob, home) { + result = Some(TrustUntrustStatus::DenyAlways); } + } else if is_path_matching_glob(workspace, glob, home) { + result = Some(TrustUntrustStatus::AllowAlways); } - Err(err) if err.kind() == std::io::ErrorKind::NotFound => (), - Err(err) => log::error!("workspace_untrust file couldn't be read: {err:?}"), - }; - TrustUntrustStatus::DenyOnce + } + + result +} + +#[test] +fn trust_from_globs_test() { + let home = PathBuf::from("/home/user"); + + let globs: Vec = vec!["!**".to_string(), "~/repos/helix".to_string()]; + let cases = vec![ + ("/foo/bar", Some(TrustUntrustStatus::DenyAlways)), + ( + "/home/user/repos/helix", + Some(TrustUntrustStatus::AllowAlways), + ), + ( + "/home/user/repos/helix/branch_a", + Some(TrustUntrustStatus::DenyAlways), + ), + ]; + + for (workspace, result) in cases { + assert_eq!( + trust_from_globs(&PathBuf::from(workspace), &home, &globs), + result, + "trust_from_globs({workspace:?}, {home:?}, globs) != {result:?}" + ); + } + + // This matches the examples given in the documentation, see + // book/src/workspace-trust.md + let globs: Vec = vec![ + "~/repos/helix".to_string(), + "~/repos/foo/*".to_string(), + "~/repos/bar/**".to_string(), + "!~/repos/bar/untrusted".to_string(), + ]; + let cases = vec![ + ("/home/user/foobar", None), + ( + "/home/user/repos/helix", + Some(TrustUntrustStatus::AllowAlways), + ), + ("/home/other/repos/helix", None), + ("/home/user/repos/helix/branch_a", None), + ("/home/user/repos/foo", None), + ( + "/home/user/repos/foo/branch_a", + Some(TrustUntrustStatus::AllowAlways), + ), + ("/home/user/repos/foo/remote_a/branch_a", None), + ( + "/home/user/repos/bar/branch_a", + Some(TrustUntrustStatus::AllowAlways), + ), + ( + "/home/user/repos/bar/remote_a/branch_a", + Some(TrustUntrustStatus::AllowAlways), + ), + ( + "/home/user/repos/bar/untrusted", + Some(TrustUntrustStatus::DenyAlways), + ), + ]; + + for (workspace, result) in cases { + assert_eq!( + trust_from_globs(&PathBuf::from(workspace), &home, &globs), + result, + "trust_from_globs({workspace:?}, {home:?}, globs) != {result:?}" + ); + } +} + +pub fn quick_query_workspace_with_explicit_untrust(config: &Config) -> Option { + let workspace = crate::find_workspace().0; + let mut cache = WORKSPACE_TRUST_CACHE.lock(); + if let Some(trust) = cache.get(&workspace) { + return Some(*trust); + } + + // trust_from_config is cheap for `trust_config.paths.len() == 0`. But + // bailing out with DenyAlways when no `$HOME` directory is available is + // better only done if there actualy is a trust configuration that does + // understand `~` as shorthand for `$HOME`. + if !config.paths.is_empty() { + let Some(home_dir) = env::home_dir() else { + log::error!("Unable to get HOME directory needed to process trust configuration. Denying trust as fallback."); + return Some(TrustUntrustStatus::DenyAlways); + }; + if let Some(trust) = trust_from_globs(&workspace, &home_dir, &config.paths) { + cache.insert(workspace, trust); + return Some(trust); + } + } + + if is_path_in_file(&workspace, &workspace_trust_file()) { + cache.insert(workspace, TrustUntrustStatus::AllowAlways); + return Some(TrustUntrustStatus::AllowAlways); + } + + if is_path_in_file(&workspace, &workspace_exclude_file()) { + cache.insert(workspace, TrustUntrustStatus::DenyAlways); + return Some(TrustUntrustStatus::DenyAlways); + } + + // NOTE: Eagerly caching DenyOnce here, so that we only return None on the + // very first call. This allows helix_loader to not have a separate + // cache to trace whether a prompt has already been shown. + cache.insert(workspace, TrustUntrustStatus::DenyOnce); + + None +} + +pub fn clear_trust_cache() { + let mut cache = WORKSPACE_TRUST_CACHE.lock(); + cache.clear(); } diff --git a/helix-term/Cargo.toml b/helix-term/Cargo.toml index 57fbeb72c3fd..d4050734405f 100644 --- a/helix-term/Cargo.toml +++ b/helix-term/Cargo.toml @@ -92,8 +92,6 @@ serde = { version = "1.0", features = ["derive"] } dashmap = "6.0" -parking_lot.workspace = true - [target.'cfg(windows)'.dependencies] crossterm = { version = "0.28", features = ["event-stream"] } diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index c4e0bc3eb126..7ccd96bc5824 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -421,7 +421,9 @@ impl Application { // Update the syntax language loader before setting the theme. Setting the theme will // call `Loader::set_scopes` which must be done before the documents are re-parsed for // the sake of locals highlighting. - let lang_loader = helix_core::config::user_lang_loader(default_config.editor.insecure)?; + let lang_loader = helix_core::config::user_lang_loader( + &helix_loader::workspace_trust::Config::default(), + )?; self.editor.syn_loader.store(Arc::new(lang_loader)); Self::load_configured_theme( &mut self.editor, diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs index 09edbfafad93..1dca737fcc67 100644 --- a/helix-term/src/config.rs +++ b/helix-term/src/config.rs @@ -126,7 +126,7 @@ impl Config { let phony_config = ConfigLoadError::Error(IOError::other("hacky placeholder")); let global_parsed = Config::load(Ok(&global_config), Err(phony_config))?; if let helix_loader::workspace_trust::TrustStatus::Trusted = - helix_loader::workspace_trust::quick_query_workspace(global_parsed.editor.insecure) + helix_loader::workspace_trust::quick_query_workspace(&global_parsed.editor.trust) { Config::load(Ok(&global_config), local_config) } else { diff --git a/helix-term/src/handlers/workspace_trust.rs b/helix-term/src/handlers/workspace_trust.rs index 68e9d8bdfffe..64736dabaad1 100644 --- a/helix-term/src/handlers/workspace_trust.rs +++ b/helix-term/src/handlers/workspace_trust.rs @@ -1,45 +1,42 @@ -use std::{collections::HashSet, path::PathBuf}; - +use arc_swap::access::Access; use helix_event::register_hook; use helix_loader::workspace_trust::{ - quick_query_workspace_with_explicit_untrust, TrustUntrustStatus, WorkspaceTrust, + clear_trust_cache, quick_query_workspace_with_explicit_untrust, TrustUntrustStatus, + WorkspaceTrust, +}; +use helix_view::{ + events::ConfigDidChange, events::DocumentDidOpen, handlers::Handlers, DocumentId, }; -use helix_view::{events::DocumentDidOpen, handlers::Handlers, DocumentId}; -use once_cell::sync::Lazy; -use parking_lot::Mutex; use crate::{compositor::Compositor, job, ui}; const ID: &str = "workspace-trust-select"; -/// A set of canonicalized workspace paths which have been prompted for trust at runtime. -static PROMPTED_WORKSPACES: Lazy>> = - Lazy::new(|| Mutex::new(HashSet::new())); - pub(super) fn register_hooks(_handlers: &Handlers) { register_hook!(move |event: &mut DocumentDidOpen<'_>| { let doc = doc!(event.editor, &event.doc); // If there is no servers to be loaded, then the workspace might not be trusted yet - if doc.language_servers().next().is_none() { - if let TrustUntrustStatus::DenyOnce = - quick_query_workspace_with_explicit_untrust(event.editor.config().insecure) - { - let (workspace, _) = helix_loader::find_workspace(); - job::dispatch_blocking(|_editor, compositor| prompt(workspace, compositor)); - } + if doc.language_servers().next().is_none() + && quick_query_workspace_with_explicit_untrust(&event.editor.config.load().trust) + .is_none() + { + job::dispatch_blocking(|_editor, compositor| prompt(compositor)); + } + Ok(()) + }); + + register_hook!(move |event: &mut ConfigDidChange<'_>| { + if event.old.trust != event.new.trust { + clear_trust_cache(); + // TODO: restart LSP, so that no `:lsp-restart` is needed after + // reloading the config } Ok(()) }); } -pub fn prompt(path: PathBuf, compositor: &mut Compositor) { - let mut workspaces = PROMPTED_WORKSPACES.lock(); - if workspaces.contains(&path) { - return; - } else { - workspaces.insert(path.clone()); - } +pub fn prompt(compositor: &mut Compositor) { let select = select(); compositor.replace_or_push(ID, select); } diff --git a/helix-term/src/health.rs b/helix-term/src/health.rs index c73df787f22b..56d5655c66fe 100644 --- a/helix-term/src/health.rs +++ b/helix-term/src/health.rs @@ -163,22 +163,23 @@ fn languages(selection: Option>) -> std::io::Result<()> { let stdout = std::io::stdout(); let mut stdout = stdout.lock(); - let mut syn_loader_conf = match user_lang_config(false) { - Ok(conf) => conf, - Err(err) => { - let stderr = std::io::stderr(); - let mut stderr = stderr.lock(); + let mut syn_loader_conf = + match user_lang_config(&helix_loader::workspace_trust::Config::default()) { + Ok(conf) => conf, + Err(err) => { + let stderr = std::io::stderr(); + let mut stderr = stderr.lock(); - writeln!( - stderr, - "{}: {}", - "Error parsing user language config".red(), - err - )?; - writeln!(stderr, "{}", "Using default language config".yellow())?; - default_lang_config() - } - }; + writeln!( + stderr, + "{}: {}", + "Error parsing user language config".red(), + err + )?; + writeln!(stderr, "{}", "Using default language config".yellow())?; + default_lang_config() + } + }; let mut headings = vec!["Language", "Language servers", "Debug adapter", "Formatter"]; @@ -283,7 +284,8 @@ pub fn language(lang_str: String) -> std::io::Result<()> { let stdout = std::io::stdout(); let mut stdout = stdout.lock(); - let syn_loader_conf = match user_lang_config(false) { + let syn_loader_conf = match user_lang_config(&helix_loader::workspace_trust::Config::default()) + { Ok(conf) => conf, Err(err) => { let stderr = std::io::stderr(); diff --git a/helix-term/src/main.rs b/helix-term/src/main.rs index 0bc87cf49fa1..b25bdc9e2c52 100644 --- a/helix-term/src/main.rs +++ b/helix-term/src/main.rs @@ -141,7 +141,7 @@ FLAGS: }; let lang_loader = - helix_core::config::user_lang_loader(config.editor.insecure).unwrap_or_else(|err| { + helix_core::config::user_lang_loader(&config.editor.trust).unwrap_or_else(|err| { eprintln!("{}", err); eprintln!("Press to continue with default language config"); use std::io::Read; diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 2146bc877fac..c925dd2f5596 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -15,7 +15,7 @@ use crate::{ Document, DocumentId, View, ViewId, }; use helix_event::dispatch; -use helix_loader::workspace_trust::TrustStatus; +use helix_loader::workspace_trust::{self, TrustStatus}; use helix_vcs::DiffProviderRegistry; use futures_util::stream::select_all::SelectAll; @@ -432,8 +432,8 @@ pub struct Config { /// Whether to enable Kitty Keyboard Protocol pub kitty_keyboard_protocol: KittyKeyboardProtocolConfig, pub buffer_picker: BufferPickerConfig, - /// Whether to implicitly trust every workspace or not - pub insecure: bool, + /// workspace trust configuration. + pub trust: workspace_trust::Config, } #[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize, Clone, Copy)] @@ -1156,7 +1156,7 @@ impl Default for Config { rainbow_brackets: false, kitty_keyboard_protocol: Default::default(), buffer_picker: BufferPickerConfig::default(), - insecure: false, + trust: workspace_trust::Config::default(), } } } @@ -1653,7 +1653,7 @@ impl Editor { let root_dirs = &config.workspace_lsp_roots; if let TrustStatus::Untrusted = - helix_loader::workspace_trust::quick_query_workspace(self.config.load().insecure) + helix_loader::workspace_trust::quick_query_workspace(&self.config.load().trust) { self.set_status( "Current workspace is not trusted. Run `:workspace-trust` to enable all features.",