use crate::effect::ExEffect;
use hjkl_engine::Host;
pub fn all_setting_names() -> Vec<String> {
vec![
"shiftwidth".into(),
"sw".into(),
"tabstop".into(),
"ts".into(),
"softtabstop".into(),
"sts".into(),
"textwidth".into(),
"tw".into(),
"undolevels".into(),
"ul".into(),
"timeoutlen".into(),
"tm".into(),
"numberwidth".into(),
"nuw".into(),
"foldcolumn".into(),
"fdc".into(),
"iskeyword".into(),
"isk".into(),
"signcolumn".into(),
"scl".into(),
"colorcolumn".into(),
"cc".into(),
"formatoptions".into(),
"fo".into(),
"filetype".into(),
"ft".into(),
"commentstring".into(),
"cms".into(),
"background".into(),
"bg".into(),
"ignorecase".into(),
"ic".into(),
"smartcase".into(),
"scs".into(),
"wrapscan".into(),
"ws".into(),
"expandtab".into(),
"et".into(),
"autoindent".into(),
"ai".into(),
"smartindent".into(),
"si".into(),
"undobreak".into(),
"readonly".into(),
"ro".into(),
"number".into(),
"nu".into(),
"relativenumber".into(),
"rnu".into(),
"cursorline".into(),
"cul".into(),
"cursorcolumn".into(),
"cuc".into(),
"wrap".into(),
"linebreak".into(),
"lbr".into(),
"foldenable".into(),
"fen".into(),
"autopair".into(),
"ap".into(),
"autoclose-tag".into(),
"act".into(),
]
}
pub(crate) fn apply_set<H: Host>(
editor: &mut hjkl_engine::Editor<hjkl_buffer::Buffer, H>,
body: &str,
) -> ExEffect {
let trimmed = body.trim();
if trimmed.is_empty() {
let s = editor.settings();
let wrap = match s.wrap {
hjkl_buffer::Wrap::None => "off",
hjkl_buffer::Wrap::Char => "char",
hjkl_buffer::Wrap::Word => "word",
};
let scl = match s.signcolumn {
hjkl_engine::types::SignColumnMode::Yes => "yes",
hjkl_engine::types::SignColumnMode::No => "no",
hjkl_engine::types::SignColumnMode::Auto => "auto",
};
return ExEffect::Info(format!(
"shiftwidth={} tabstop={} softtabstop={} textwidth={} undolevels={} timeoutlen={} iskeyword=\"{}\" expandtab={} ignorecase={} smartcase={} wrapscan={} autoindent={} smartindent={} undobreak={} readonly={} wrap={} number={} relativenumber={} numberwidth={} cursorline={} cursorcolumn={} signcolumn={} foldcolumn={} colorcolumn=\"{}\" formatoptions=\"{}\" filetype=\"{}\" commentstring=\"{}\" autopair={} autoclose-tag={}",
s.shiftwidth,
s.tabstop,
s.softtabstop,
s.textwidth,
s.undo_levels,
s.timeout_len.as_millis(),
s.iskeyword,
if s.expandtab { "on" } else { "off" },
if s.ignore_case { "on" } else { "off" },
if s.smartcase { "on" } else { "off" },
if s.wrapscan { "on" } else { "off" },
if s.autoindent { "on" } else { "off" },
if s.smartindent { "on" } else { "off" },
if s.undo_break_on_motion { "on" } else { "off" },
if s.readonly { "on" } else { "off" },
wrap,
if s.number { "on" } else { "off" },
if s.relativenumber { "on" } else { "off" },
s.numberwidth,
if s.cursorline { "on" } else { "off" },
if s.cursorcolumn { "on" } else { "off" },
scl,
s.foldcolumn,
s.colorcolumn,
s.formatoptions,
s.filetype,
s.commentstring,
if s.autopair { "on" } else { "off" },
if s.autoclose_tag { "on" } else { "off" },
));
}
let mut query_lines: Vec<String> = Vec::new();
for token in trimmed.split_whitespace() {
if let Some(name) = token.strip_suffix('?') {
match query_option_value(editor, name) {
Some(v) => query_lines.push(format!("{name}={v}")),
None => return ExEffect::Error(format!("unknown :set option `{name}`")),
}
continue;
}
if let Err(e) = apply_set_token(editor, token) {
return ExEffect::Error(e);
}
}
if !query_lines.is_empty() {
return ExEffect::Info(query_lines.join(" "));
}
ExEffect::Ok
}
fn query_option_value<H: Host>(
editor: &hjkl_engine::Editor<hjkl_buffer::Buffer, H>,
name: &str,
) -> Option<String> {
let s = editor.settings();
let on_off = |b: bool| if b { "on" } else { "off" }.to_string();
Some(match name {
"shiftwidth" | "sw" => s.shiftwidth.to_string(),
"tabstop" | "ts" => s.tabstop.to_string(),
"softtabstop" | "sts" => s.softtabstop.to_string(),
"textwidth" | "tw" => s.textwidth.to_string(),
"undolevels" | "ul" => s.undo_levels.to_string(),
"timeoutlen" | "tm" => s.timeout_len.as_millis().to_string(),
"numberwidth" | "nuw" => s.numberwidth.to_string(),
"foldcolumn" | "fdc" => s.foldcolumn.to_string(),
"iskeyword" | "isk" => format!("\"{}\"", s.iskeyword),
"colorcolumn" | "cc" => format!("\"{}\"", s.colorcolumn),
"formatoptions" | "fo" => format!("\"{}\"", s.formatoptions),
"filetype" | "ft" => format!("\"{}\"", s.filetype),
"commentstring" | "cms" => format!("\"{}\"", s.commentstring),
"signcolumn" | "scl" => match s.signcolumn {
hjkl_engine::types::SignColumnMode::Yes => "yes".into(),
hjkl_engine::types::SignColumnMode::No => "no".into(),
hjkl_engine::types::SignColumnMode::Auto => "auto".into(),
},
"wrap" => match s.wrap {
hjkl_buffer::Wrap::None => "off".into(),
hjkl_buffer::Wrap::Char => "char".into(),
hjkl_buffer::Wrap::Word => "word".into(),
},
"expandtab" | "et" => on_off(s.expandtab),
"ignorecase" | "ic" => on_off(s.ignore_case),
"smartcase" | "scs" => on_off(s.smartcase),
"wrapscan" | "ws" => on_off(s.wrapscan),
"autoindent" | "ai" => on_off(s.autoindent),
"smartindent" | "si" => on_off(s.smartindent),
"undobreak" => on_off(s.undo_break_on_motion),
"readonly" | "ro" => on_off(s.readonly),
"number" | "nu" => on_off(s.number),
"relativenumber" | "rnu" => on_off(s.relativenumber),
"cursorline" | "cul" => on_off(s.cursorline),
"cursorcolumn" | "cuc" => on_off(s.cursorcolumn),
"autopair" | "ap" => on_off(s.autopair),
"autoclose-tag" | "act" => on_off(s.autoclose_tag),
_ => return None,
})
}
fn apply_set_token<H: Host>(
editor: &mut hjkl_engine::Editor<hjkl_buffer::Buffer, H>,
token: &str,
) -> Result<(), String> {
if let Some(rest) = token
.strip_prefix("formatoptions+=")
.or_else(|| token.strip_prefix("fo+="))
{
for ch in rest.chars() {
if !editor.settings().formatoptions.contains(ch) {
editor.settings_mut().formatoptions.push(ch);
}
}
return Ok(());
}
if let Some(rest) = token
.strip_prefix("formatoptions-=")
.or_else(|| token.strip_prefix("fo-="))
{
for ch in rest.chars() {
let fo = editor.settings().formatoptions.clone();
editor.settings_mut().formatoptions = fo.chars().filter(|&c| c != ch).collect();
}
return Ok(());
}
if let Some((name, value)) = token.split_once('=') {
if matches!(name, "iskeyword" | "isk") {
editor.set_iskeyword(value);
return Ok(());
}
if matches!(name, "signcolumn" | "scl") {
editor.settings_mut().signcolumn = match value {
"yes" => hjkl_engine::types::SignColumnMode::Yes,
"no" => hjkl_engine::types::SignColumnMode::No,
"auto" => hjkl_engine::types::SignColumnMode::Auto,
other => {
return Err(format!(
"signcolumn must be `yes`, `no`, or `auto`, got `{other}`"
));
}
};
return Ok(());
}
if matches!(name, "colorcolumn" | "cc") {
editor.settings_mut().colorcolumn = value.to_string();
return Ok(());
}
if matches!(name, "formatoptions" | "fo") {
editor.settings_mut().formatoptions = value.to_string();
return Ok(());
}
if matches!(name, "filetype" | "ft") {
editor.settings_mut().filetype = value.to_string();
return Ok(());
}
if matches!(name, "commentstring" | "cms") {
editor.settings_mut().commentstring = value.to_string();
return Ok(());
}
let parsed: usize = value
.parse()
.map_err(|_| format!("bad value `{value}` for :set {name}"))?;
match name {
"shiftwidth" | "sw" => {
if parsed == 0 {
return Err("shiftwidth must be > 0".into());
}
editor.settings_mut().shiftwidth = parsed;
}
"tabstop" | "ts" => {
if parsed == 0 {
return Err("tabstop must be > 0".into());
}
editor.settings_mut().tabstop = parsed;
}
"textwidth" | "tw" => {
if parsed == 0 {
return Err("textwidth must be > 0".into());
}
editor.settings_mut().textwidth = parsed;
}
"undolevels" | "ul" => {
editor.settings_mut().undo_levels = parsed.min(u32::MAX as usize) as u32;
}
"timeoutlen" | "tm" => {
editor.settings_mut().timeout_len =
core::time::Duration::from_millis(parsed as u64);
}
"numberwidth" | "nuw" => {
if !(1..=20).contains(&parsed) {
return Err(format!("numberwidth must be in range 1..=20, got {parsed}"));
}
editor.settings_mut().numberwidth = parsed;
}
"foldcolumn" | "fdc" => {
if parsed > 12 {
return Err(format!("foldcolumn must be in range 0..=12, got {parsed}"));
}
editor.settings_mut().foldcolumn = parsed as u32;
}
other => return Err(format!("unknown :set option `{other}`")),
}
return Ok(());
}
if let Some(name) = token.strip_suffix('!') {
match name {
"number" | "nu" => {
editor.settings_mut().number = !editor.settings().number;
}
"relativenumber" | "rnu" => {
editor.settings_mut().relativenumber = !editor.settings().relativenumber;
}
"cursorline" | "cul" => {
editor.settings_mut().cursorline = !editor.settings().cursorline;
}
"cursorcolumn" | "cuc" => {
editor.settings_mut().cursorcolumn = !editor.settings().cursorcolumn;
}
other => return Err(format!("unknown :set option `{other}`")),
}
return Ok(());
}
let (name, value) = if let Some(rest) = token.strip_prefix("no") {
(rest, false)
} else {
(token, true)
};
match name {
"ignorecase" | "ic" => editor.settings_mut().ignore_case = value,
"smartcase" | "scs" => editor.settings_mut().smartcase = value,
"wrapscan" | "ws" => editor.settings_mut().wrapscan = value,
"expandtab" | "et" => editor.settings_mut().expandtab = value,
"autoindent" | "ai" => editor.settings_mut().autoindent = value,
"smartindent" | "si" => editor.settings_mut().smartindent = value,
"undobreak" => editor.settings_mut().undo_break_on_motion = value,
"readonly" | "ro" => editor.settings_mut().readonly = value,
"number" | "nu" => editor.settings_mut().number = value,
"relativenumber" | "rnu" => editor.settings_mut().relativenumber = value,
"cursorline" | "cul" => editor.settings_mut().cursorline = value,
"cursorcolumn" | "cuc" => editor.settings_mut().cursorcolumn = value,
"wrap" => {
editor.settings_mut().wrap = if value {
match editor.settings().wrap {
hjkl_buffer::Wrap::Word => hjkl_buffer::Wrap::Word,
_ => hjkl_buffer::Wrap::Char,
}
} else {
hjkl_buffer::Wrap::None
};
}
"linebreak" | "lbr" => {
editor.settings_mut().wrap = if value {
hjkl_buffer::Wrap::Word
} else {
match editor.settings().wrap {
hjkl_buffer::Wrap::None => hjkl_buffer::Wrap::None,
_ => hjkl_buffer::Wrap::Char,
}
};
}
"autopair" | "ap" => editor.settings_mut().autopair = value,
"autoclose-tag" | "act" => editor.settings_mut().autoclose_tag = value,
"foldenable" | "fen" | "background" | "bg" => {}
other => return Err(format!("unknown :set option `{other}`")),
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use hjkl_engine::{DefaultHost, Editor, Options};
fn make_editor() -> Editor<hjkl_buffer::Buffer, DefaultHost> {
let buf = hjkl_buffer::Buffer::new();
let host = DefaultHost::new();
Editor::new(buf, host, Options::default())
}
#[test]
fn set_bare_returns_info_with_shiftwidth() {
let mut editor = make_editor();
let result = apply_set(&mut editor, "");
match result {
ExEffect::Info(s) => assert!(
s.contains("shiftwidth="),
"bare :set info missing shiftwidth=, got: {s}"
),
other => panic!("expected Info(_), got {other:?}"),
}
}
#[test]
fn set_number_enables_number() {
let mut editor = make_editor();
assert_eq!(apply_set(&mut editor, "number"), ExEffect::Ok);
assert!(editor.settings().number);
}
#[test]
fn set_nonumber_disables_number() {
let mut editor = make_editor();
editor.settings_mut().number = true;
assert_eq!(apply_set(&mut editor, "nonumber"), ExEffect::Ok);
assert!(!editor.settings().number);
}
#[test]
fn set_nu_alias_enables_number() {
let mut editor = make_editor();
assert_eq!(apply_set(&mut editor, "nu"), ExEffect::Ok);
assert!(editor.settings().number);
}
#[test]
fn set_nonu_alias_disables_number() {
let mut editor = make_editor();
editor.settings_mut().number = true;
assert_eq!(apply_set(&mut editor, "nonu"), ExEffect::Ok);
assert!(!editor.settings().number);
}
#[test]
fn set_number_bang_toggles_number_off() {
let mut editor = make_editor();
editor.settings_mut().number = true;
assert_eq!(apply_set(&mut editor, "number!"), ExEffect::Ok);
assert!(!editor.settings().number);
}
#[test]
fn set_number_bang_toggles_number_on() {
let mut editor = make_editor();
editor.settings_mut().number = false;
assert_eq!(apply_set(&mut editor, "number!"), ExEffect::Ok);
assert!(editor.settings().number);
}
#[test]
fn set_nu_bang_toggles_number() {
let mut editor = make_editor();
editor.settings_mut().number = true;
assert_eq!(apply_set(&mut editor, "nu!"), ExEffect::Ok);
assert!(!editor.settings().number);
}
#[test]
fn set_scrolloff_eq_5() {
let mut editor = make_editor();
assert_eq!(apply_set(&mut editor, "tabstop=5"), ExEffect::Ok);
assert_eq!(editor.settings().tabstop, 5);
}
#[test]
fn set_ts_alias_sets_tabstop() {
let mut editor = make_editor();
assert_eq!(apply_set(&mut editor, "ts=4"), ExEffect::Ok);
assert_eq!(editor.settings().tabstop, 4);
}
#[test]
fn set_tabstop_eq_4() {
let mut editor = make_editor();
assert_eq!(apply_set(&mut editor, "tabstop=4"), ExEffect::Ok);
assert_eq!(editor.settings().tabstop, 4);
}
#[test]
fn set_shiftwidth_eq_2() {
let mut editor = make_editor();
assert_eq!(apply_set(&mut editor, "shiftwidth=2"), ExEffect::Ok);
assert_eq!(editor.settings().shiftwidth, 2);
}
#[test]
fn set_sw_alias_sets_shiftwidth() {
let mut editor = make_editor();
assert_eq!(apply_set(&mut editor, "sw=8"), ExEffect::Ok);
assert_eq!(editor.settings().shiftwidth, 8);
}
#[test]
fn set_ignorecase_enables() {
let mut editor = make_editor();
assert_eq!(apply_set(&mut editor, "ignorecase"), ExEffect::Ok);
assert!(editor.settings().ignore_case);
}
#[test]
fn set_ic_alias_enables_ignorecase() {
let mut editor = make_editor();
assert_eq!(apply_set(&mut editor, "ic"), ExEffect::Ok);
assert!(editor.settings().ignore_case);
}
#[test]
fn set_noic_disables_ignorecase() {
let mut editor = make_editor();
editor.settings_mut().ignore_case = true;
assert_eq!(apply_set(&mut editor, "noic"), ExEffect::Ok);
assert!(!editor.settings().ignore_case);
}
#[test]
fn set_iskeyword_stored_verbatim() {
let mut editor = make_editor();
assert_eq!(apply_set(&mut editor, "iskeyword=@,a-z"), ExEffect::Ok);
assert_eq!(editor.settings().iskeyword, "@,a-z");
}
#[test]
fn set_isk_alias_stores_iskeyword() {
let mut editor = make_editor();
assert_eq!(apply_set(&mut editor, "isk=0-9"), ExEffect::Ok);
assert_eq!(editor.settings().iskeyword, "0-9");
}
#[test]
fn set_signcolumn_yes() {
let mut editor = make_editor();
assert_eq!(apply_set(&mut editor, "signcolumn=yes"), ExEffect::Ok);
assert_eq!(
editor.settings().signcolumn,
hjkl_engine::types::SignColumnMode::Yes
);
}
#[test]
fn set_scl_alias_auto() {
let mut editor = make_editor();
assert_eq!(apply_set(&mut editor, "scl=auto"), ExEffect::Ok);
assert_eq!(
editor.settings().signcolumn,
hjkl_engine::types::SignColumnMode::Auto
);
}
#[test]
fn set_scl_invalid_returns_error() {
let mut editor = make_editor();
let result = apply_set(&mut editor, "scl=invalid");
assert!(
matches!(result, ExEffect::Error(_)),
"expected Error, got {result:?}"
);
}
#[test]
fn set_bad_name_returns_error_containing_name() {
let mut editor = make_editor();
let result = apply_set(&mut editor, "badname");
match result {
ExEffect::Error(s) => assert!(
s.contains("badname"),
"error should mention badname, got: {s}"
),
other => panic!("expected Error(_), got {other:?}"),
}
}
#[test]
fn set_textwidth_and_tw_alias() {
let mut editor = make_editor();
assert_eq!(apply_set(&mut editor, "textwidth=80"), ExEffect::Ok);
assert_eq!(editor.settings().textwidth, 80);
assert_eq!(apply_set(&mut editor, "tw=100"), ExEffect::Ok);
assert_eq!(editor.settings().textwidth, 100);
}
#[test]
fn set_numberwidth_eq_6() {
let mut editor = make_editor();
assert_eq!(apply_set(&mut editor, "numberwidth=6"), ExEffect::Ok);
assert_eq!(editor.settings().numberwidth, 6);
}
#[test]
fn set_nuw_alias_sets_numberwidth() {
let mut editor = make_editor();
assert_eq!(apply_set(&mut editor, "nuw=3"), ExEffect::Ok);
assert_eq!(editor.settings().numberwidth, 3);
}
#[test]
fn set_relativenumber_enables() {
let mut editor = make_editor();
assert_eq!(apply_set(&mut editor, "relativenumber"), ExEffect::Ok);
assert!(editor.settings().relativenumber);
}
#[test]
fn set_rnu_alias_enables_relativenumber() {
let mut editor = make_editor();
assert_eq!(apply_set(&mut editor, "rnu"), ExEffect::Ok);
assert!(editor.settings().relativenumber);
}
#[test]
fn set_colorcolumn_stored() {
let mut editor = make_editor();
assert_eq!(apply_set(&mut editor, "colorcolumn=80"), ExEffect::Ok);
assert_eq!(editor.settings().colorcolumn, "80");
}
#[test]
fn set_cc_alias_stores_colorcolumn() {
let mut editor = make_editor();
assert_eq!(apply_set(&mut editor, "cc=100"), ExEffect::Ok);
assert_eq!(editor.settings().colorcolumn, "100");
}
#[test]
fn all_setting_names_contains_background() {
let names = super::all_setting_names();
assert!(
names.iter().any(|n| n == "background"),
"all_setting_names() must include \"background\" for :set Tab-completion"
);
}
#[test]
fn set_formatoptions_equals() {
let mut editor = make_editor();
assert_eq!(apply_set(&mut editor, "formatoptions=o"), ExEffect::Ok);
assert_eq!(editor.settings().formatoptions, "o");
}
#[test]
fn set_fo_alias() {
let mut editor = make_editor();
assert_eq!(apply_set(&mut editor, "fo=r"), ExEffect::Ok);
assert_eq!(editor.settings().formatoptions, "r");
}
#[test]
fn set_fo_append_flag() {
let mut editor = make_editor();
editor.settings_mut().formatoptions = "r".to_string();
assert_eq!(apply_set(&mut editor, "fo+=o"), ExEffect::Ok);
assert!(editor.settings().formatoptions.contains('o'));
assert!(editor.settings().formatoptions.contains('r'));
}
#[test]
fn set_fo_remove_flag() {
let mut editor = make_editor();
editor.settings_mut().formatoptions = "ro".to_string();
assert_eq!(apply_set(&mut editor, "fo-=r"), ExEffect::Ok);
assert!(!editor.settings().formatoptions.contains('r'));
assert!(editor.settings().formatoptions.contains('o'));
}
#[test]
fn set_fo_append_no_duplicate() {
let mut editor = make_editor();
editor.settings_mut().formatoptions = "r".to_string();
assert_eq!(apply_set(&mut editor, "fo+=r"), ExEffect::Ok);
assert_eq!(
editor
.settings()
.formatoptions
.chars()
.filter(|&c| c == 'r')
.count(),
1
);
}
#[test]
fn set_filetype_stores_lang() {
let mut editor = make_editor();
assert_eq!(apply_set(&mut editor, "filetype=rust"), ExEffect::Ok);
assert_eq!(editor.settings().filetype, "rust");
}
#[test]
fn set_ft_alias_stores_lang() {
let mut editor = make_editor();
assert_eq!(apply_set(&mut editor, "ft=python"), ExEffect::Ok);
assert_eq!(editor.settings().filetype, "python");
}
#[test]
fn bare_set_shows_formatoptions_and_filetype() {
let mut editor = make_editor();
editor.settings_mut().filetype = "rust".to_string();
let result = apply_set(&mut editor, "");
match result {
ExEffect::Info(s) => {
assert!(
s.contains("formatoptions="),
"missing formatoptions in :set output"
);
assert!(s.contains("filetype="), "missing filetype in :set output");
}
other => panic!("expected Info(_), got {other:?}"),
}
}
#[test]
fn all_setting_names_contains_formatoptions() {
let names = super::all_setting_names();
assert!(names.iter().any(|n| n == "formatoptions"));
assert!(names.iter().any(|n| n == "fo"));
}
#[test]
fn all_setting_names_contains_filetype() {
let names = super::all_setting_names();
assert!(names.iter().any(|n| n == "filetype"));
assert!(names.iter().any(|n| n == "ft"));
}
#[test]
fn set_filetype_query_returns_info() {
let mut editor = make_editor();
editor.settings_mut().filetype = "rust".to_string();
match apply_set(&mut editor, "filetype?") {
ExEffect::Info(s) => assert_eq!(s, "filetype=\"rust\""),
other => panic!("expected Info(_), got {other:?}"),
}
}
#[test]
fn set_ft_alias_query_works() {
let mut editor = make_editor();
editor.settings_mut().filetype = "html".to_string();
match apply_set(&mut editor, "ft?") {
ExEffect::Info(s) => assert_eq!(s, "ft=\"html\""),
other => panic!("expected Info(_), got {other:?}"),
}
}
#[test]
fn set_bool_query_reports_on_off() {
let mut editor = make_editor();
editor.settings_mut().autopair = true;
match apply_set(&mut editor, "autopair?") {
ExEffect::Info(s) => assert_eq!(s, "autopair=on"),
other => panic!("expected Info(_), got {other:?}"),
}
}
#[test]
fn set_int_query_reports_number() {
let mut editor = make_editor();
editor.settings_mut().shiftwidth = 4;
match apply_set(&mut editor, "sw?") {
ExEffect::Info(s) => assert_eq!(s, "sw=4"),
other => panic!("expected Info(_), got {other:?}"),
}
}
#[test]
fn set_unknown_query_errors() {
let mut editor = make_editor();
match apply_set(&mut editor, "bogus?") {
ExEffect::Error(s) => assert!(s.contains("bogus"), "got {s:?}"),
other => panic!("expected Error(_), got {other:?}"),
}
}
#[test]
fn set_mixed_query_and_apply_returns_info() {
let mut editor = make_editor();
editor.settings_mut().filetype = "rust".to_string();
match apply_set(&mut editor, "number filetype?") {
ExEffect::Info(s) => {
assert_eq!(s, "filetype=\"rust\"");
assert!(editor.settings().number, "number must be applied alongside");
}
other => panic!("expected Info(_), got {other:?}"),
}
}
}