use std::{collections::HashSet, path::PathBuf}; use helix_event::register_hook; use helix_loader::workspace_trust::{ quick_query_workspace_with_explicit_untrust, TrustUntrustStatus, WorkspaceTrust, }; 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)); } } 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()); } let select = select(); compositor.replace_or_push(ID, select); } const TRUST_MESSAGE: &str = "Trust this workspace? Trusted workspaces may load local config files and auto-start language servers. Config and language servers can execute arbitrary code. Only trust workspaces which you know contain harmless config and code."; fn select() -> ui::Select { ui::Select::new( TRUST_MESSAGE, [ TrustUntrustStatus::DenyOnce, TrustUntrustStatus::DenyAlways, TrustUntrustStatus::AllowAlways, ], (), move |editor, option, event| { if event == ui::PromptEvent::Validate { let mut trust = WorkspaceTrust::load(true); match option { TrustUntrustStatus::DenyAlways => { trust.exclude_workspace(); } TrustUntrustStatus::DenyOnce => { // Do nothing } TrustUntrustStatus::AllowAlways => { trust.trust_workspace(); let documents: Vec = editor.documents.keys().cloned().collect(); for document_id in documents.iter() { editor.launch_language_servers(*document_id); } let _ = editor .config_events .0 .send(helix_view::editor::ConfigEvent::Refresh); } } } }, ) } impl crate::ui::menu::Item for TrustUntrustStatus { type Data = (); fn format(&self, _data: &Self::Data) -> tui::widgets::Row<'_> { match self { TrustUntrustStatus::DenyAlways => "Never", TrustUntrustStatus::DenyOnce => "Not now", TrustUntrustStatus::AllowAlways => "Always", } .into() } }