Skip to content

Commit fd863a8

Browse files
committed
Add rudimentary workspace trust support
1 parent b7fbae5 commit fd863a8

15 files changed

Lines changed: 437 additions & 42 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

helix-core/src/config.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,14 @@ impl std::fmt::Display for LanguageLoaderError {
3737
impl std::error::Error for LanguageLoaderError {}
3838

3939
/// Language configuration based on user configured languages.toml.
40-
pub fn user_lang_config() -> Result<Configuration, toml::de::Error> {
41-
helix_loader::config::user_lang_config()?.try_into()
40+
pub fn user_lang_config(insecure: bool) -> Result<Configuration, toml::de::Error> {
41+
helix_loader::config::user_lang_config(insecure)?.try_into()
4242
}
4343

4444
/// Language configuration loader based on user configured languages.toml.
45-
pub fn user_lang_loader() -> Result<Loader, LanguageLoaderError> {
46-
let config_val =
47-
helix_loader::config::user_lang_config().map_err(LanguageLoaderError::DeserializeError)?;
45+
pub fn user_lang_loader(insecure: bool) -> Result<Loader, LanguageLoaderError> {
46+
let config_val = helix_loader::config::user_lang_config(insecure)
47+
.map_err(LanguageLoaderError::DeserializeError)?;
4848
let config = config_val.clone().try_into().map_err(|e| {
4949
if let Some(languages) = config_val.get("language").and_then(|v| v.as_array()) {
5050
for lang in languages.iter() {

helix-loader/src/config.rs

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use std::str::from_utf8;
22

3+
use crate::workspace_trust::{quick_query_workspace, TrustStatus};
4+
35
/// Default built-in languages.toml.
46
pub fn default_lang_config() -> toml::Value {
57
let default_config = include_bytes!("../../languages.toml");
@@ -8,23 +10,34 @@ pub fn default_lang_config() -> toml::Value {
810
}
911

1012
/// User configured languages.toml file, merged with the default config.
11-
pub fn user_lang_config() -> Result<toml::Value, toml::de::Error> {
12-
let config = [
13-
crate::config_dir(),
14-
crate::find_workspace().0.join(".helix"),
15-
]
16-
.into_iter()
17-
.map(|path| path.join("languages.toml"))
18-
.filter_map(|file| {
19-
std::fs::read_to_string(file)
20-
.map(|config| toml::from_str(&config))
21-
.ok()
22-
})
23-
.collect::<Result<Vec<_>, _>>()?
24-
.into_iter()
25-
.fold(default_lang_config(), |a, b| {
26-
crate::merge_toml_values(a, b, 3)
27-
});
13+
pub fn user_lang_config(insecure: bool) -> Result<toml::Value, toml::de::Error> {
14+
let global_config = crate::lang_config_file();
15+
let workspace_config = crate::workspace_lang_config_file();
16+
17+
let trusted = if let TrustStatus::Trusted = quick_query_workspace() {
18+
true
19+
} else {
20+
false
21+
};
22+
23+
let files = if trusted || insecure {
24+
vec![global_config, workspace_config]
25+
} else {
26+
vec![global_config]
27+
};
28+
29+
let config = files
30+
.iter()
31+
.filter_map(|file| {
32+
std::fs::read_to_string(file)
33+
.map(|config| toml::from_str(&config))
34+
.ok()
35+
})
36+
.collect::<Result<Vec<_>, _>>()?
37+
.into_iter()
38+
.fold(default_lang_config(), |a, b| {
39+
crate::merge_toml_values(a, b, 3)
40+
});
2841

2942
Ok(config)
3043
}

helix-loader/src/grammar.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ pub fn build_grammars(target: Option<String>) -> Result<()> {
195195
// merged. The `grammar_selection` key of the config is then used to filter
196196
// down all grammars into a subset of the user's choosing.
197197
fn get_grammar_configs() -> Result<Vec<GrammarConfiguration>> {
198-
let config: Configuration = crate::config::user_lang_config()
198+
let config: Configuration = crate::config::user_lang_config(false)
199199
.context("Could not parse languages.toml")?
200200
.try_into()?;
201201

@@ -217,7 +217,7 @@ fn get_grammar_configs() -> Result<Vec<GrammarConfiguration>> {
217217
}
218218

219219
pub fn get_grammar_names() -> Result<Option<HashSet<String>>> {
220-
let config: Configuration = crate::config::user_lang_config()
220+
let config: Configuration = crate::config::user_lang_config(false)
221221
.context("Could not parse languages.toml")?
222222
.try_into()?;
223223

helix-loader/src/lib.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
pub mod config;
22
pub mod grammar;
3+
pub mod workspace_trust;
34

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

@@ -132,6 +133,13 @@ pub fn cache_dir() -> PathBuf {
132133
path
133134
}
134135

136+
pub fn data_dir() -> PathBuf {
137+
let strategy = choose_base_strategy().expect("Unable to find the config directory!");
138+
let mut path = strategy.data_dir();
139+
path.push("helix");
140+
path
141+
}
142+
135143
pub fn config_file() -> PathBuf {
136144
CONFIG_FILE.get().map(|path| path.to_path_buf()).unwrap()
137145
}
@@ -144,6 +152,10 @@ pub fn workspace_config_file() -> PathBuf {
144152
find_workspace().0.join(".helix").join("config.toml")
145153
}
146154

155+
pub fn workspace_lang_config_file() -> PathBuf {
156+
find_workspace().0.join(".helix").join("languages.toml")
157+
}
158+
147159
pub fn lang_config_file() -> PathBuf {
148160
config_dir().join("languages.toml")
149161
}
@@ -152,6 +164,14 @@ pub fn default_log_file() -> PathBuf {
152164
cache_dir().join("helix.log")
153165
}
154166

167+
pub fn workspace_trust_file() -> PathBuf {
168+
data_dir().join("trusted_workspaces")
169+
}
170+
171+
pub fn workspace_exclude_file() -> PathBuf {
172+
data_dir().join("excluded_workspaces")
173+
}
174+
155175
/// Merge two TOML documents, merging values from `right` onto `left`
156176
///
157177
/// `merge_depth` sets the nesting depth up to which values are merged instead
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
use std::{collections::HashSet, fs, path::PathBuf};
2+
3+
use crate::{data_dir, workspace_exclude_file, workspace_trust_file};
4+
5+
pub struct WorkspaceTrust {
6+
trusted: HashSet<PathBuf>,
7+
excluded: Option<HashSet<PathBuf>>,
8+
}
9+
10+
#[derive(Clone, Copy)]
11+
pub enum TrustStatus {
12+
Untrusted,
13+
Trusted,
14+
}
15+
16+
impl WorkspaceTrust {
17+
/// Loads `WorkspaceTrust`.
18+
///
19+
/// Should be used only when there is a need to change trust status
20+
/// of a particular workspace.
21+
///
22+
/// For querying trust status of a workspace use `quick_query_workspace()` or
23+
/// `quick_query_workspace_with_explicit_untrust()`
24+
pub fn load(with_exclusion: bool) -> Self {
25+
let mut trusted = HashSet::new();
26+
27+
match fs::read_to_string(workspace_trust_file()) {
28+
Ok(workspace_trust_file) => {
29+
for line in workspace_trust_file.split('\n') {
30+
if !line.is_empty() {
31+
let path = PathBuf::from(line);
32+
trusted.insert(path);
33+
}
34+
}
35+
}
36+
Err(e) => log::error!("workspace file couldn't be read: {:?}", e),
37+
};
38+
39+
let excluded = if with_exclusion {
40+
let mut untrusted = HashSet::new();
41+
42+
match fs::read_to_string(workspace_exclude_file()) {
43+
Ok(workspace_untrust_file) => {
44+
for line in workspace_untrust_file.split('\n') {
45+
if !line.is_empty() {
46+
let path = PathBuf::from(line);
47+
untrusted.insert(path);
48+
}
49+
}
50+
}
51+
Err(e) => log::error!("workspace file couldn't be read: {:?}", e),
52+
};
53+
54+
Some(untrusted)
55+
} else {
56+
None
57+
};
58+
WorkspaceTrust { trusted, excluded }
59+
}
60+
61+
fn write_trust_to_file(&self) {
62+
let mut trust_text = String::new();
63+
for workspace in self.trusted.iter() {
64+
if let Some(path_str) = workspace.to_str() {
65+
trust_text += &format!("{path_str}\n");
66+
}
67+
}
68+
// let chains aren't supported in current MSRV
69+
if let Ok(false) = fs::exists(data_dir()) {
70+
if let Err(e) = fs::create_dir_all(data_dir()) {
71+
log::error!("Couldn't create helix's data directory: {:?}", e);
72+
};
73+
}
74+
if let Err(e) = fs::write(workspace_trust_file(), trust_text) {
75+
log::error!("Error during write of workspace_trust file: {:?}", e);
76+
}
77+
}
78+
79+
fn write_exclusion_to_file(&self) {
80+
if let Some(untrusted) = &self.excluded {
81+
let mut trust_text = String::new();
82+
for workspace in untrusted.iter() {
83+
if let Some(path_str) = workspace.to_str() {
84+
trust_text += &format!("{path_str}\n");
85+
}
86+
}
87+
// let chains aren't supported in current MSRV
88+
if let Ok(false) = fs::exists(data_dir()) {
89+
if let Err(e) = fs::create_dir_all(data_dir()) {
90+
log::error!("Couldn't create helix's data directory: {:?}", e);
91+
};
92+
}
93+
if let Err(e) = fs::write(workspace_exclude_file(), trust_text) {
94+
log::error!("Error during write of workspace_trust file: {:?}", e);
95+
}
96+
} else {
97+
log::error!("Called write_untrust_to_file() when self.untrusted is None");
98+
}
99+
}
100+
101+
/// Mark current workspace trusted
102+
pub fn trust_workspace(&mut self) {
103+
let workspace = crate::find_workspace().0;
104+
self.trusted.insert(workspace);
105+
self.write_trust_to_file();
106+
}
107+
108+
/// Remove trusted mark from current workspace
109+
pub fn untrust_workspace(&mut self) {
110+
let workspace = crate::find_workspace().0;
111+
self.trusted.remove(&workspace);
112+
self.write_trust_to_file();
113+
}
114+
115+
/// Mark current workspace excluded.
116+
///
117+
/// Should be called only if `WorkspaceTrust` was created with `WorkspaceTrust::load(true)`
118+
pub fn exclude_workspace(&mut self) {
119+
let workspace = crate::find_workspace().0;
120+
self.trusted.remove(&workspace);
121+
if let Some(excluded) = &mut self.excluded {
122+
excluded.insert(workspace);
123+
self.write_exclusion_to_file();
124+
} else {
125+
log::error!("Called untrust_workspace_permanent() when self.untrusted is None");
126+
}
127+
}
128+
}
129+
130+
#[derive(Default, Clone, Copy, Debug)]
131+
pub enum TrustUntrustStatus {
132+
DenyAlways,
133+
#[default]
134+
DenyOnce,
135+
AllowAlways,
136+
}
137+
138+
pub fn quick_query_workspace() -> TrustStatus {
139+
let workspace = crate::find_workspace().0;
140+
match fs::read_to_string(workspace_trust_file()) {
141+
Ok(workspace_trust_file) => {
142+
for line in workspace_trust_file.split('\n') {
143+
if PathBuf::from(line) == workspace {
144+
return TrustStatus::Trusted;
145+
}
146+
}
147+
}
148+
Err(e) => log::error!("workspace file couldn't be read: {:?}", e),
149+
};
150+
TrustStatus::Untrusted
151+
}
152+
153+
pub fn quick_query_workspace_with_explicit_untrust() -> TrustUntrustStatus {
154+
let workspace = crate::find_workspace().0;
155+
match fs::read_to_string(workspace_trust_file()) {
156+
Ok(workspace_trust_file) => {
157+
for line in workspace_trust_file.split('\n') {
158+
if PathBuf::from(line) == workspace {
159+
return TrustUntrustStatus::AllowAlways;
160+
}
161+
}
162+
}
163+
Err(e) => log::error!("workspace_trust file couldn't be read: {:?}", e),
164+
};
165+
166+
match fs::read_to_string(workspace_exclude_file()) {
167+
Ok(workspace_untrust_file) => {
168+
for line in workspace_untrust_file.split('\n') {
169+
if PathBuf::from(line) == workspace {
170+
return TrustUntrustStatus::DenyAlways;
171+
}
172+
}
173+
}
174+
Err(e) => log::error!("workspace_untrust file couldn't be read: {:?}", e),
175+
};
176+
TrustUntrustStatus::DenyOnce
177+
}

helix-term/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ serde = { version = "1.0", features = ["derive"] }
9292

9393
dashmap = "6.0"
9494

95+
parking_lot.workspace = true
96+
9597
[target.'cfg(windows)'.dependencies]
9698
crossterm = { version = "0.28", features = ["event-stream"] }
9799

helix-term/src/application.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -417,7 +417,7 @@ impl Application {
417417
// Update the syntax language loader before setting the theme. Setting the theme will
418418
// call `Loader::set_scopes` which must be done before the documents are re-parsed for
419419
// the sake of locals highlighting.
420-
let lang_loader = helix_core::config::user_lang_loader()?;
420+
let lang_loader = helix_core::config::user_lang_loader(default_config.editor.insecure)?;
421421
self.editor.syn_loader.store(Arc::new(lang_loader));
422422
Self::load_configured_theme(
423423
&mut self.editor,

0 commit comments

Comments
 (0)