use std::fs;
use std::path::{Path, PathBuf};
use anyhow::Result;
use anyhow::{Context, anyhow};
use toml::Value as TomlValue;
use vtcode_core::config::current_config_defaults;
use vtcode_core::config::loader::ConfigManager;
use vtcode_core::config::loader::layers::ConfigLayerSource;
use vtcode_core::config::{StatusLineConfig, StatusLineMode};
use vtcode_core::utils::ansi::MessageStyle;
use vtcode_tui::app::{InlineListItem, InlineListSelection, WizardModalMode, WizardStep};
use crate::agent::runloop::slash_commands::StatuslineTargetMode;
use crate::agent::runloop::unified::palettes::refresh_runtime_config_from_manager;
use crate::agent::runloop::unified::turn::session::slash_commands::{
SlashCommandContext, SlashCommandControl,
};
use crate::agent::runloop::unified::wizard_modal::{
WizardModalOutcome, show_wizard_modal_and_wait,
};
use super::super::config_toml::{
ensure_child_table, load_toml_value, preferred_workspace_config_path, save_toml_value,
};
use super::{config_action_item, ensure_selection_ui_available, wait_for_list_modal_selection};
const STATUSLINE_INPUT_ID: &str = "statusline.input";
const STATUSLINE_SCRIPT_FILE_NAME: &str = "statusline.sh";
const STATUSLINE_SCRIPT_TEMPLATE: &str = r#"#!/bin/sh
payload="$(cat)"
branch="$(printf '%s' "$payload" | jq -r '.git.branch // ""' 2>/dev/null)"
dirty="$(printf '%s' "$payload" | jq -r '.git.dirty // false' 2>/dev/null)"
model="$(printf '%s' "$payload" | jq -r '.model.display_name // .model.id // ""' 2>/dev/null)"
reasoning="$(printf '%s' "$payload" | jq -r '.runtime.reasoning_effort // ""' 2>/dev/null)"
git_part=""
if [ -n "$branch" ]; then
git_part="$branch"
if [ "$dirty" = "true" ]; then
git_part="$git_part*"
fi
fi
model_part="$model"
if [ -n "$reasoning" ]; then
model_part="$model_part ($reasoning)"
fi
if [ -n "$git_part" ] && [ -n "$model_part" ]; then
printf '%s | %s\n' "$git_part" "$model_part"
elif [ -n "$git_part" ]; then
printf '%s\n' "$git_part"
elif [ -n "$model_part" ]; then
printf '%s\n' "$model_part"
fi
"#;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum StatuslineSetupAction {
Continue,
Save,
Cancel,
EditCommand,
UseScriptPath,
ClearCommand,
EditRefreshInterval,
EditTimeout,
ScaffoldScript { replace_existing: bool },
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum ScriptScaffoldResult {
Created,
Replaced,
SkippedExisting,
}
pub(crate) async fn handle_start_statusline_setup(
mut ctx: SlashCommandContext<'_>,
_instructions: Option<String>,
) -> Result<SlashCommandControl> {
if !ctx.renderer.supports_inline_ui() {
ctx.renderer.line(
MessageStyle::Info,
"Status line setup is available in inline UI only.",
)?;
return Ok(SlashCommandControl::Continue);
}
if !ensure_selection_ui_available(&mut ctx, "configuring the status line")? {
return Ok(SlashCommandControl::Continue);
}
let Some(target) = select_statusline_target(&mut ctx).await? else {
ctx.renderer
.line(MessageStyle::Info, "Status line setup cancelled.")?;
return Ok(SlashCommandControl::Continue);
};
let manager = ConfigManager::load_from_workspace(&ctx.config.workspace)
.context("Failed to load VT Code configuration")?;
let config_path = match target {
StatuslineTargetMode::User => preferred_user_config_path(&manager)
.context("Could not resolve user config path for status line setup")?,
StatuslineTargetMode::Workspace => {
preferred_workspace_config_path(&manager, &ctx.config.workspace)
}
};
let script_path = statusline_script_path(target, &ctx.config.workspace, &config_path);
let script_command = default_script_command(target, &script_path);
let mut draft = ctx
.vt_cfg
.as_ref()
.map(|cfg| cfg.ui.status_line.clone())
.unwrap_or_default();
loop {
let script_exists = script_path.exists();
let preview = build_statusline_preview(
&draft,
ctx.input_status_state
.git_summary
.as_ref()
.filter(|summary| !summary.branch.trim().is_empty())
.map(|summary| (summary.branch.as_str(), summary.dirty)),
ctx.input_status_state.thread_context.as_deref(),
ctx.input_status_state.ide_context_source.as_deref(),
&ctx.config.model,
);
let config_label = match target {
StatuslineTargetMode::User => "user",
StatuslineTargetMode::Workspace => "workspace",
};
let command_label = draft
.command
.as_deref()
.map(str::trim)
.filter(|value| !value.is_empty())
.map(ToString::to_string)
.unwrap_or_else(|| "(unset)".to_string());
let mode_label = statusline_mode_id(&draft.mode);
let script_state = if script_exists { "present" } else { "missing" };
ctx.handle.show_list_modal(
"Status line setup".to_string(),
vec![
format!("Configure [ui.status_line] in the {config_label} config layer."),
format!(
"Mode: {mode_label} | command: {command_label} | refresh: {}ms | timeout: {}ms",
draft.refresh_interval_ms, draft.command_timeout_ms
),
format!("Script: {} ({script_state})", script_path.display()),
format!("Preview: {preview}"),
],
build_statusline_setup_items(&draft, script_exists),
Some(InlineListSelection::ConfigAction(
"statusline:save".to_string(),
)),
None,
);
let Some(selection) = wait_for_list_modal_selection(&mut ctx).await else {
ctx.renderer
.line(MessageStyle::Info, "Status line setup cancelled.")?;
return Ok(SlashCommandControl::Continue);
};
let InlineListSelection::ConfigAction(action) = selection else {
ctx.renderer.line(
MessageStyle::Error,
"Unsupported status line setup selection.",
)?;
continue;
};
match apply_statusline_action(&action, &mut draft)? {
StatuslineSetupAction::Continue => {}
StatuslineSetupAction::Save => {
persist_statusline_config(
&mut ctx,
&config_path,
draft.clone(),
target,
script_path.as_path(),
)
.await?;
ctx.renderer.line(
MessageStyle::Info,
&format!(
"Saved status line configuration to {}.",
config_path.display()
),
)?;
return Ok(SlashCommandControl::Continue);
}
StatuslineSetupAction::Cancel => {
ctx.renderer
.line(MessageStyle::Info, "Status line setup cancelled.")?;
return Ok(SlashCommandControl::Continue);
}
StatuslineSetupAction::EditCommand => {
let current = draft
.command
.as_deref()
.map(str::trim)
.filter(|value| !value.is_empty())
.map(ToString::to_string);
let Some(value) = prompt_statusline_input(
&mut ctx,
"Status line command",
"Enter command to run with `sh -c`.",
"Command",
&script_command,
current,
false,
)
.await?
else {
continue;
};
draft.command = Some(value);
draft.mode = StatusLineMode::Command;
}
StatuslineSetupAction::UseScriptPath => {
draft.command = Some(script_command.clone());
draft.mode = StatusLineMode::Command;
ctx.renderer.line(
MessageStyle::Info,
&format!("Command set to `{}`.", script_command),
)?;
}
StatuslineSetupAction::ClearCommand => {
draft.command = None;
ctx.renderer
.line(MessageStyle::Info, "Cleared status line command.")?;
}
StatuslineSetupAction::EditRefreshInterval => {
let default_value = draft.refresh_interval_ms.to_string();
let Some(value) = prompt_statusline_input(
&mut ctx,
"Refresh interval",
"Enter status line refresh interval in milliseconds.",
"Refresh interval (ms)",
&default_value,
Some(default_value.clone()),
false,
)
.await?
else {
continue;
};
draft.refresh_interval_ms = parse_statusline_millis(&value, "refresh interval")?;
}
StatuslineSetupAction::EditTimeout => {
let default_value = draft.command_timeout_ms.to_string();
let Some(value) = prompt_statusline_input(
&mut ctx,
"Command timeout",
"Enter command timeout in milliseconds.",
"Command timeout (ms)",
&default_value,
Some(default_value.clone()),
false,
)
.await?
else {
continue;
};
draft.command_timeout_ms = parse_statusline_millis(&value, "command timeout")?;
}
StatuslineSetupAction::ScaffoldScript { replace_existing } => {
match scaffold_statusline_script(&script_path, replace_existing)? {
ScriptScaffoldResult::Created => {
ctx.renderer.line(
MessageStyle::Info,
&format!("Created status line script at {}.", script_path.display()),
)?;
}
ScriptScaffoldResult::Replaced => {
ctx.renderer.line(
MessageStyle::Info,
&format!("Replaced status line script at {}.", script_path.display()),
)?;
}
ScriptScaffoldResult::SkippedExisting => {
ctx.renderer.line(
MessageStyle::Info,
"Script already exists. Choose \"Replace script template\" to overwrite it.",
)?;
continue;
}
}
draft.command = Some(script_command.clone());
draft.mode = StatusLineMode::Command;
}
}
}
}
async fn select_statusline_target(
ctx: &mut SlashCommandContext<'_>,
) -> Result<Option<StatuslineTargetMode>> {
ctx.handle.show_list_modal(
"Status line setup".to_string(),
vec![
"Choose where VT Code should persist status line changes.".to_string(),
"User writes to your home config and ~/.config/vtcode/statusline.sh.".to_string(),
"Workspace writes to the current workspace and .vtcode/statusline.sh.".to_string(),
],
vec![
InlineListItem {
title: "User config".to_string(),
subtitle: Some("Personal VT Code setup in your user config layer".to_string()),
badge: Some("User".to_string()),
indent: 0,
selection: Some(InlineListSelection::ConfigAction(
"statusline:user".to_string(),
)),
search_value: Some("statusline user home personal".to_string()),
},
InlineListItem {
title: "Workspace config".to_string(),
subtitle: Some("Repo-local setup in the current workspace".to_string()),
badge: Some("Workspace".to_string()),
indent: 0,
selection: Some(InlineListSelection::ConfigAction(
"statusline:workspace".to_string(),
)),
search_value: Some("statusline workspace repo local".to_string()),
},
],
Some(InlineListSelection::ConfigAction(
"statusline:user".to_string(),
)),
None,
);
let Some(selection) = wait_for_list_modal_selection(ctx).await else {
return Ok(None);
};
let target = match selection {
InlineListSelection::ConfigAction(action) if action == "statusline:user" => {
StatuslineTargetMode::User
}
InlineListSelection::ConfigAction(action) if action == "statusline:workspace" => {
StatuslineTargetMode::Workspace
}
_ => {
ctx.renderer.line(
MessageStyle::Error,
"Unsupported status line setup selection.",
)?;
return Ok(None);
}
};
Ok(Some(target))
}
fn build_statusline_setup_items(
draft: &StatusLineConfig,
script_exists: bool,
) -> Vec<InlineListItem> {
let mut items = Vec::new();
for (mode, label, subtitle) in [
(
StatusLineMode::Auto,
"Use auto mode",
"Show VT Code-built status components.",
),
(
StatusLineMode::Command,
"Use command mode",
"Run a shell command and render its first output line.",
),
(
StatusLineMode::Hidden,
"Hide status line",
"Disable the bottom status line.",
),
] {
let active = draft.mode == mode;
items.push(config_action_item(
label,
subtitle,
if active { "Active" } else { "Mode" },
0,
format!("statusline:mode:{}", statusline_mode_id(&mode)),
format!("statusline mode {}", statusline_mode_id(&mode)),
));
}
items.push(config_action_item(
"Edit command",
"Set the shell command for command mode.",
"Command",
0,
"statusline:command:edit",
"statusline command edit",
));
items.push(config_action_item(
"Use scaffold script path",
"Point command to the target statusline.sh script.",
"Command",
0,
"statusline:command:script",
"statusline command script",
));
if draft
.command
.as_deref()
.map(str::trim)
.is_some_and(|value| !value.is_empty())
{
items.push(config_action_item(
"Clear command",
"Remove command so command mode falls back to auto.",
"Command",
0,
"statusline:command:clear",
"statusline command clear",
));
}
if script_exists {
items.push(config_action_item(
"Replace script template",
"Overwrite existing statusline.sh with the default template.",
"Script",
0,
"statusline:script:replace",
"statusline script replace",
));
} else {
items.push(config_action_item(
"Create script template",
"Create statusline.sh using the default JSON payload template.",
"Script",
0,
"statusline:script:create",
"statusline script create",
));
}
items.push(config_action_item(
&format!("Refresh interval: {}ms", draft.refresh_interval_ms),
"Set command refresh cadence.",
"Timing",
0,
"statusline:refresh:edit",
"statusline refresh interval",
));
items.push(config_action_item(
&format!("Command timeout: {}ms", draft.command_timeout_ms),
"Set command execution timeout.",
"Timing",
0,
"statusline:timeout:edit",
"statusline timeout",
));
items.push(config_action_item(
"Save changes",
"Persist [ui.status_line] changes.",
"Save",
0,
"statusline:save",
"statusline save",
));
items.push(config_action_item(
"Cancel",
"Discard changes.",
"Cancel",
0,
"statusline:cancel",
"statusline cancel",
));
items
}
fn apply_statusline_action(
action: &str,
draft: &mut StatusLineConfig,
) -> Result<StatuslineSetupAction> {
match action {
"statusline:save" => return Ok(StatuslineSetupAction::Save),
"statusline:cancel" => return Ok(StatuslineSetupAction::Cancel),
"statusline:command:edit" => return Ok(StatuslineSetupAction::EditCommand),
"statusline:command:script" => return Ok(StatuslineSetupAction::UseScriptPath),
"statusline:command:clear" => return Ok(StatuslineSetupAction::ClearCommand),
"statusline:refresh:edit" => return Ok(StatuslineSetupAction::EditRefreshInterval),
"statusline:timeout:edit" => return Ok(StatuslineSetupAction::EditTimeout),
"statusline:script:create" => {
return Ok(StatuslineSetupAction::ScaffoldScript {
replace_existing: false,
});
}
"statusline:script:replace" => {
return Ok(StatuslineSetupAction::ScaffoldScript {
replace_existing: true,
});
}
_ => {}
}
if let Some(mode) = action.strip_prefix("statusline:mode:") {
draft.mode = match mode {
"auto" => StatusLineMode::Auto,
"command" => StatusLineMode::Command,
"hidden" => StatusLineMode::Hidden,
_ => return Err(anyhow!("unsupported status line mode action: {mode}")),
};
return Ok(StatuslineSetupAction::Continue);
}
Err(anyhow!("unsupported status line action: {action}"))
}
fn build_statusline_preview(
draft: &StatusLineConfig,
git: Option<(&str, bool)>,
thread_context: Option<&str>,
ide_context_source: Option<&str>,
model: &str,
) -> String {
match draft.mode {
StatusLineMode::Hidden => "hidden mode: status line disabled".to_string(),
StatusLineMode::Command => {
let command = draft
.command
.as_deref()
.map(str::trim)
.filter(|value| !value.is_empty())
.unwrap_or("(unset)");
format!("command mode (setup does not execute command): {command}")
}
StatusLineMode::Auto => {
let mut left_parts = Vec::new();
if let Some((branch, dirty)) = git {
let trimmed = branch.trim();
if !trimmed.is_empty() {
left_parts.push(if dirty {
format!("{trimmed}*")
} else {
trimmed.to_string()
});
}
}
let mut right_parts = Vec::new();
let mut seen = std::collections::HashSet::new();
for value in [thread_context, ide_context_source, Some(model)] {
let Some(value) = value.map(str::trim).filter(|value| !value.is_empty()) else {
continue;
};
let key = value.to_ascii_lowercase();
if seen.insert(key) {
right_parts.push(value.to_string());
}
}
if left_parts.is_empty() && right_parts.is_empty() {
return "auto mode: waiting for runtime context".to_string();
}
if left_parts.is_empty() {
return format!("auto mode: {}", right_parts.join(" | "));
}
if right_parts.is_empty() {
return format!("auto mode: {}", left_parts.join(" | "));
}
format!(
"auto mode: {} | {}",
left_parts.join(" | "),
right_parts.join(" | ")
)
}
}
}
fn statusline_mode_id(mode: &StatusLineMode) -> &'static str {
match mode {
StatusLineMode::Auto => "auto",
StatusLineMode::Command => "command",
StatusLineMode::Hidden => "hidden",
}
}
async fn prompt_statusline_input(
ctx: &mut SlashCommandContext<'_>,
title: &str,
question: &str,
freeform_label: &str,
placeholder: &str,
default_value: Option<String>,
allow_empty: bool,
) -> Result<Option<String>> {
let step = build_statusline_prompt_step(question, freeform_label, placeholder, default_value);
let outcome = show_wizard_modal_and_wait(
ctx.handle,
ctx.session,
title.to_string(),
vec![step],
0,
None,
WizardModalMode::MultiStep,
ctx.ctrl_c_state,
ctx.ctrl_c_notify,
)
.await?;
let value = match outcome {
WizardModalOutcome::Submitted(selections) => {
selections
.into_iter()
.find_map(|selection| match selection {
InlineListSelection::RequestUserInputAnswer {
question_id,
selected,
other,
} if question_id == STATUSLINE_INPUT_ID => {
other.or_else(|| selected.first().cloned())
}
_ => None,
})
}
WizardModalOutcome::Cancelled { .. } => None,
};
let Some(value) = value else {
return Ok(None);
};
let trimmed = value.trim().to_string();
if trimmed.is_empty() && !allow_empty {
ctx.renderer
.line(MessageStyle::Info, "Input was empty. Nothing changed.")?;
return Ok(None);
}
if trimmed.is_empty() {
return Ok(Some(String::new()));
}
Ok(Some(trimmed))
}
fn build_statusline_prompt_step(
question: &str,
freeform_label: &str,
placeholder: &str,
default_value: Option<String>,
) -> WizardStep {
WizardStep {
title: "Input".to_string(),
question: question.to_string(),
items: vec![InlineListItem {
title: "Submit".to_string(),
subtitle: Some(
"Press Enter to accept the default, or Tab to type a custom value.".to_string(),
),
badge: None,
indent: 0,
selection: Some(InlineListSelection::RequestUserInputAnswer {
question_id: STATUSLINE_INPUT_ID.to_string(),
selected: vec![],
other: Some(String::new()),
}),
search_value: Some("submit statusline input".to_string()),
}],
completed: false,
answer: None,
allow_freeform: true,
freeform_label: Some(freeform_label.to_string()),
freeform_placeholder: Some(placeholder.to_string()),
freeform_default: default_value,
}
}
fn parse_statusline_millis(value: &str, label: &str) -> Result<u64> {
value
.trim()
.parse::<u64>()
.with_context(|| format!("Failed to parse {} as milliseconds", label))
}
async fn persist_statusline_config(
ctx: &mut SlashCommandContext<'_>,
config_path: &Path,
draft: StatusLineConfig,
target: StatuslineTargetMode,
script_path: &Path,
) -> Result<()> {
write_statusline_config(config_path, &draft)?;
refresh_runtime_config_from_manager(
ctx.renderer,
ctx.handle,
ctx.config,
ctx.vt_cfg,
ctx.provider_client.as_ref(),
ctx.session_bootstrap,
ctx.full_auto,
)
.await?;
if target == StatuslineTargetMode::Workspace {
let command = draft
.command
.as_deref()
.map(str::trim)
.unwrap_or_default()
.to_string();
if command == default_script_command(target, script_path) && !script_path.exists() {
ctx.renderer.line(
MessageStyle::Warning,
"Saved command path points to a missing script. Use \"Create script template\" to scaffold it.",
)?;
}
}
Ok(())
}
fn write_statusline_config(config_path: &Path, draft: &StatusLineConfig) -> Result<()> {
let mut root = load_toml_value(config_path)?;
let root_table = root
.as_table_mut()
.context("Status line config root is not a TOML table")?;
let ui_table = ensure_child_table(root_table, "ui");
let status_table = ensure_child_table(ui_table, "status_line");
status_table.insert(
"mode".to_string(),
TomlValue::String(statusline_mode_id(&draft.mode).to_string()),
);
if let Some(command) = draft
.command
.as_deref()
.map(str::trim)
.filter(|value| !value.is_empty())
{
status_table.insert(
"command".to_string(),
TomlValue::String(command.to_string()),
);
} else {
status_table.remove("command");
}
status_table.insert(
"refresh_interval_ms".to_string(),
TomlValue::Integer(u64_to_toml_integer(
draft.refresh_interval_ms,
"refresh_interval_ms",
)?),
);
status_table.insert(
"command_timeout_ms".to_string(),
TomlValue::Integer(u64_to_toml_integer(
draft.command_timeout_ms,
"command_timeout_ms",
)?),
);
save_toml_value(config_path, &root)
}
fn u64_to_toml_integer(value: u64, label: &str) -> Result<i64> {
i64::try_from(value).with_context(|| format!("{label} is too large to persist"))
}
fn statusline_script_path(
target: StatuslineTargetMode,
workspace: &Path,
config_path: &Path,
) -> PathBuf {
match target {
StatuslineTargetMode::Workspace => {
workspace.join(".vtcode").join(STATUSLINE_SCRIPT_FILE_NAME)
}
StatuslineTargetMode::User => config_path
.parent()
.map(Path::to_path_buf)
.unwrap_or_else(|| workspace.to_path_buf())
.join(STATUSLINE_SCRIPT_FILE_NAME),
}
}
fn default_script_command(target: StatuslineTargetMode, script_path: &Path) -> String {
match target {
StatuslineTargetMode::Workspace => ".vtcode/statusline.sh".to_string(),
StatuslineTargetMode::User => shell_quote(script_path),
}
}
fn shell_quote(path: &Path) -> String {
let path = path.to_string_lossy();
format!("'{}'", path.replace('\'', "'\\''"))
}
fn preferred_user_config_path(manager: &ConfigManager) -> Option<PathBuf> {
manager
.layer_stack()
.layers()
.iter()
.rev()
.find_map(|layer| match &layer.source {
ConfigLayerSource::User { file } if layer.is_enabled() => Some(file.clone()),
_ => None,
})
.or_else(|| {
let defaults = current_config_defaults();
defaults
.home_config_paths(manager.config_file_name())
.into_iter()
.next()
})
.or_else(|| dirs::home_dir().map(|home| home.join(manager.config_file_name())))
}
fn scaffold_statusline_script(path: &Path, replace_existing: bool) -> Result<ScriptScaffoldResult> {
if path.exists() && !replace_existing {
return Ok(ScriptScaffoldResult::SkippedExisting);
}
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)
.with_context(|| format!("Failed to create {}", parent.display()))?;
}
let result = if path.exists() {
ScriptScaffoldResult::Replaced
} else {
ScriptScaffoldResult::Created
};
fs::write(path, STATUSLINE_SCRIPT_TEMPLATE)
.with_context(|| format!("Failed to write {}", path.display()))?;
set_executable(path)?;
Ok(result)
}
#[cfg(unix)]
fn set_executable(path: &Path) -> Result<()> {
use std::os::unix::fs::PermissionsExt;
let mut permissions = fs::metadata(path)
.with_context(|| format!("Failed to read metadata for {}", path.display()))?
.permissions();
permissions.set_mode(0o755);
fs::set_permissions(path, permissions)
.with_context(|| format!("Failed to set executable bit on {}", path.display()))
}
#[cfg(not(unix))]
fn set_executable(_path: &Path) -> Result<()> {
Ok(())
}
#[cfg(test)]
mod tests {
use std::path::Path;
use vtcode_core::config::{StatusLineConfig, StatusLineMode};
use super::{
StatuslineSetupAction, apply_statusline_action, build_statusline_preview,
build_statusline_setup_items, default_script_command,
};
use crate::agent::runloop::slash_commands::StatuslineTargetMode;
#[test]
fn statusline_action_switches_mode() {
let mut draft = StatusLineConfig::default();
let action =
apply_statusline_action("statusline:mode:hidden", &mut draft).expect("mode action");
assert_eq!(action, StatuslineSetupAction::Continue);
assert_eq!(draft.mode, StatusLineMode::Hidden);
}
#[test]
fn statusline_preview_in_command_mode_never_executes() {
let draft = StatusLineConfig {
mode: StatusLineMode::Command,
command: Some(".vtcode/statusline.sh".to_string()),
..StatusLineConfig::default()
};
let preview = build_statusline_preview(
&draft,
Some(("main", false)),
Some("thread-1"),
None,
"gpt-5.4",
);
assert_eq!(
preview,
"command mode (setup does not execute command): .vtcode/statusline.sh"
);
}
#[test]
fn statusline_items_offer_replace_when_script_exists() {
let draft = StatusLineConfig::default();
let items = build_statusline_setup_items(&draft, true);
assert!(
items
.iter()
.any(|item| item.title == "Replace script template")
);
assert!(
!items
.iter()
.any(|item| item.title == "Create script template")
);
}
#[test]
fn user_script_command_is_shell_quoted() {
let command = default_script_command(
StatuslineTargetMode::User,
Path::new("/tmp/status line's/script.sh"),
);
assert_eq!(command, "'/tmp/status line'\\''s/script.sh'");
}
}