diff --git a/book/src/generated/typable-cmd.md b/book/src/generated/typable-cmd.md index bb21fd6b..f12082bb 100644 --- a/book/src/generated/typable-cmd.md +++ b/book/src/generated/typable-cmd.md @@ -41,3 +41,4 @@ | `:hsplit`, `:hs`, `:sp` | Open the file in a horizontal split. | | `:tutor` | Open the tutorial. | | `:goto`, `:g` | Go to line number. | +| `:set-option`, `:set` | Set a config option at runtime | diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index ee6a5989..7b1235f8 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -2637,6 +2637,36 @@ pub mod cmd { let (view, doc) = current!(cx.editor); view.ensure_cursor_in_view(doc, line); + Ok(()) + } + + fn setting( + cx: &mut compositor::Context, + args: &[Cow], + _event: PromptEvent, + ) -> anyhow::Result<()> { + let runtime_config = &mut cx.editor.config; + + if args.len() != 2 { + anyhow::bail!("Bad arguments. Usage: `:set key field`"); + } + + let (key, arg) = (&args[0].to_lowercase(), &args[1]); + + match key.as_ref() { + "scrolloff" => runtime_config.scrolloff = arg.parse()?, + "scroll-lines" => runtime_config.scroll_lines = arg.parse()?, + "mouse" => runtime_config.mouse = arg.parse()?, + "line-number" => runtime_config.line_number = arg.parse()?, + "middle-click_paste" => runtime_config.middle_click_paste = arg.parse()?, + "smart-case" => runtime_config.smart_case = arg.parse()?, + "auto-pairs" => runtime_config.auto_pairs = arg.parse()?, + "auto-completion" => runtime_config.auto_completion = arg.parse()?, + "completion-trigger-len" => runtime_config.completion_trigger_len = arg.parse()?, + "auto-info" => runtime_config.auto_info = arg.parse()?, + "true-color" => runtime_config.true_color = arg.parse()?, + _ => anyhow::bail!("Unknown key `{}`.", args[0]), + } Ok(()) } @@ -2928,6 +2958,13 @@ pub mod cmd { doc: "Go to line number.", fun: goto_line_number, completer: None, + }, + TypableCommand { + name: "set-option", + aliases: &["set"], + doc: "Set a config option at runtime", + fun: setting, + completer: Some(completers::setting), } ]; diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs index f57e2e2b..9e096311 100644 --- a/helix-term/src/ui/mod.rs +++ b/helix-term/src/ui/mod.rs @@ -174,7 +174,9 @@ pub mod completers { use crate::ui::prompt::Completion; use fuzzy_matcher::skim::SkimMatcherV2 as Matcher; use fuzzy_matcher::FuzzyMatcher; + use helix_view::editor::Config; use helix_view::theme; + use once_cell::sync::Lazy; use std::borrow::Cow; use std::cmp::Reverse; @@ -208,6 +210,31 @@ pub mod completers { names } + pub fn setting(input: &str) -> Vec { + static KEYS: Lazy> = Lazy::new(|| { + serde_json::to_value(Config::default()) + .unwrap() + .as_object() + .unwrap() + .keys() + .cloned() + .collect() + }); + + let matcher = Matcher::default(); + + let mut matches: Vec<_> = KEYS + .iter() + .filter_map(|name| matcher.fuzzy_match(name, input).map(|score| (name, score))) + .collect(); + + matches.sort_unstable_by_key(|(_file, score)| Reverse(*score)); + matches + .into_iter() + .map(|(name, _)| ((0..), name.into())) + .collect() + } + pub fn filename(input: &str) -> Vec { filename_impl(input, |entry| { let is_dir = entry.file_type().map_or(false, |entry| entry.is_dir()); diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index fd6eb4d5..f4b0f73e 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -27,7 +27,7 @@ pub use helix_core::register::Registers; use helix_core::syntax; use helix_core::{Position, Selection}; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; fn deserialize_duration_millis<'de, D>(deserializer: D) -> Result where @@ -37,7 +37,7 @@ where Ok(Duration::from_millis(millis)) } -#[derive(Debug, Clone, PartialEq, Deserialize)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "kebab-case", default, deny_unknown_fields)] pub struct FilePickerConfig { /// IgnoreOptions @@ -77,7 +77,7 @@ impl Default for FilePickerConfig { } } -#[derive(Debug, Clone, PartialEq, Deserialize)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "kebab-case", default, deny_unknown_fields)] pub struct Config { /// Padding to keep between the edge of the screen and the cursor when scrolling. Defaults to 5. @@ -109,7 +109,7 @@ pub struct Config { pub true_color: bool, } -#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] pub enum LineNumber { /// Show absolute line number @@ -119,6 +119,18 @@ pub enum LineNumber { Relative, } +impl std::str::FromStr for LineNumber { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "absolute" | "abs" => Ok(Self::Absolute), + "relative" | "rel" => Ok(Self::Relative), + _ => anyhow::bail!("Line number can only be `absolute` or `relative`."), + } + } +} + impl Default for Config { fn default() -> Self { Self {