Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions artifacts/arf.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
"default": {
"auto_match": true,
"auto_suggestions": "all",
"bottom_margin": "disabled",
"highlight_matching_bracket": false,
"key_map": {
"Alt-Hyphen": " <- ",
Expand Down Expand Up @@ -171,6 +172,50 @@
}
]
},
"BottomMargin": {
"description": "Bottom margin configuration to keep prompt away from terminal bottom.\n\nControls how much space to reserve at the bottom of the terminal.",
"default": "disabled",
"oneOf": [
{
"description": "Disabled (default) - no bottom margin",
"type": "string",
"enum": [
"disabled"
]
Comment on lines +182 to +184
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the world of TOML, where there are no nulls, I think not setting anything represents null.

I think that instead of setting a special value "disabled" here, the default should be to not display the line.

},
{
"description": "Fixed number of lines to reserve at bottom",
"type": "object",
"properties": {
"fixed": {
"description": "Number of lines to reserve at bottom (0 to terminal height)",
"type": "integer",
"minimum": 0
}
},
"additionalProperties": false,
"required": [
"fixed"
]
},
{
"description": "Fraction of terminal height to reserve",
"type": "object",
"properties": {
"proportional": {
"description": "Fraction of terminal height (0.0 = top, 1.0 = bottom/disabled)",
"type": "number",
"maximum": 1.0,
"minimum": 0.0
}
},
"additionalProperties": false,
"required": [
"proportional"
]
}
]
},
"ColorsConfig": {
"description": "Color configuration for syntax highlighting and prompts. Colors can be named (e.g., 'Red', 'DarkGray'), 256-color ({ Fixed: 99 }), or RGB ({ Rgb: [255, 0, 0] }).",
"type": "object",
Expand Down Expand Up @@ -1503,6 +1548,11 @@
"$ref": "#/$defs/AutoSuggestions",
"default": "all"
},
"bottom_margin": {
"description": "Bottom margin to keep prompt away from terminal bottom.\n\n- `disabled`: No margin (default)\n- `{ fixed = 10 }`: Reserve 10 lines at bottom\n- `{ proportional = 0.5 }`: Reserve bottom 50% of terminal",
"$ref": "#/$defs/BottomMargin",
"default": "disabled"
},
"highlight_matching_bracket": {
"description": "Highlight matching bracket when cursor is on a bracket.",
"type": "boolean",
Expand Down
169 changes: 169 additions & 0 deletions crates/arf-console/src/config/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,167 @@ impl<'de> Deserialize<'de> for AutoSuggestions {
}
}

/// Bottom margin configuration to keep prompt away from terminal bottom.
///
/// Controls how much space to reserve at the bottom of the terminal.
#[derive(Debug, Clone, Copy, PartialEq, Default, JsonSchema)]
#[schemars(schema_with = "bottom_margin_schema")]
pub enum BottomMargin {
/// Fixed number of lines to reserve at bottom.
Fixed(u16),
/// Fraction of terminal height (0.0-1.0).
Proportional(f32),
/// Disabled (default) - no bottom margin.
#[default]
Disabled,
}

impl fmt::Display for BottomMargin {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
BottomMargin::Fixed(n) => write!(f, "fixed({})", n),
BottomMargin::Proportional(v) => write!(f, "proportional({})", v),
BottomMargin::Disabled => write!(f, "disabled"),
}
}
}

/// Custom JSON schema for BottomMargin.
fn bottom_margin_schema(_generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
schemars::json_schema!({
"description": "Bottom margin to keep prompt away from terminal bottom. Can be a string \"disabled\" or an object with type and value.",
"oneOf": [
{
"type": "string",
"enum": ["disabled"],
"description": "Disabled (default) - no bottom margin"
},
{
"type": "object",
"description": "Fixed number of lines to reserve at bottom",
"properties": {
"fixed": {
"type": "integer",
"description": "Number of lines to reserve at bottom (0 to terminal height)",
"minimum": 0
}
},
"required": ["fixed"],
"additionalProperties": false
},
{
"type": "object",
"description": "Fraction of terminal height to reserve",
"properties": {
"proportional": {
"type": "number",
"description": "Fraction of terminal height (0.0 = top, 1.0 = bottom/disabled)",
"minimum": 0.0,
"maximum": 1.0
}
},
"required": ["proportional"],
"additionalProperties": false
}
],
"default": "disabled"
})
}

// Serialize BottomMargin - can be string "disabled" or object with fixed/proportional.
impl Serialize for BottomMargin {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
use serde::ser::SerializeMap;
match self {
BottomMargin::Disabled => serializer.serialize_str("disabled"),
BottomMargin::Fixed(n) => {
let mut map = serializer.serialize_map(Some(1))?;
map.serialize_entry("fixed", n)?;
map.end()
}
BottomMargin::Proportional(v) => {
let mut map = serializer.serialize_map(Some(1))?;
map.serialize_entry("proportional", v)?;
map.end()
}
}
}
}

// Deserialize BottomMargin - accepts "disabled" string or object with fixed/proportional.
impl<'de> Deserialize<'de> for BottomMargin {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
use serde::de::{self, MapAccess, Visitor};

struct BottomMarginVisitor;

impl<'de> Visitor<'de> for BottomMarginVisitor {
type Value = BottomMargin;

fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str(
"a string \"disabled\" or an object with \"fixed\" or \"proportional\" key",
)
}

fn visit_str<E>(self, value: &str) -> Result<BottomMargin, E>
where
E: de::Error,
{
match value.to_lowercase().as_str() {
"disabled" => Ok(BottomMargin::Disabled),
_ => Err(de::Error::unknown_variant(value, &["disabled"])),
}
}

fn visit_map<M>(self, mut map: M) -> Result<BottomMargin, M::Error>
where
M: MapAccess<'de>,
{
let mut fixed: Option<u16> = None;
let mut proportional: Option<f32> = None;

while let Some(key) = map.next_key::<String>()? {
match key.as_str() {
"fixed" => {
if fixed.is_some() {
return Err(de::Error::duplicate_field("fixed"));
}
fixed = Some(map.next_value()?);
}
"proportional" => {
if proportional.is_some() {
return Err(de::Error::duplicate_field("proportional"));
}
proportional = Some(map.next_value()?);
}
_ => {
return Err(de::Error::unknown_field(&key, &["fixed", "proportional"]));
}
}
}

match (fixed, proportional) {
(Some(n), None) => Ok(BottomMargin::Fixed(n)),
(None, Some(v)) => Ok(BottomMargin::Proportional(v)),
(None, None) => Err(de::Error::missing_field("fixed or proportional")),
(Some(_), Some(_)) => Err(de::Error::custom(
"cannot specify both fixed and proportional",
)),
}
}
}

deserializer.deserialize_any(BottomMarginVisitor)
}
}

/// Editor configuration.
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(default)]
Expand Down Expand Up @@ -159,6 +320,13 @@ pub struct EditorConfig {
#[serde(default = "default_key_map")]
#[schemars(schema_with = "key_map_schema")]
pub key_map: BTreeMap<KeyCombination, String>,
/// Bottom margin to keep prompt away from terminal bottom.
///
/// - `disabled`: No margin (default)
/// - `{ fixed = 10 }`: Reserve 10 lines at bottom
/// - `{ proportional = 0.5 }`: Reserve bottom 50% of terminal
#[serde(default)]
pub bottom_margin: BottomMargin,
}

fn default_key_map() -> BTreeMap<KeyCombination, String> {
Expand Down Expand Up @@ -196,6 +364,7 @@ impl Default for EditorConfig {
highlight_matching_bracket: false,
auto_suggestions: AutoSuggestions::All,
key_map: default_key_map(),
bottom_margin: BottomMargin::default(),
}
}
}
38 changes: 37 additions & 1 deletion crates/arf-console/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ mod startup;

pub use colors::{ColorsConfig, MetaColorConfig, RColorConfig, StatusColorConfig, ViColorConfig};
pub use completion::CompletionConfig;
pub use editor::{AutoSuggestions, EditorConfig, EditorMode};
pub use editor::{AutoSuggestions, BottomMargin, EditorConfig, EditorMode};
pub use experimental::{
ExperimentalConfig, HistoryForgetConfig, PromptDurationConfig, SpinnerConfig,
};
Expand Down Expand Up @@ -986,4 +986,40 @@ auto_match = false
// Restore permissions so tempdir cleanup succeeds
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o644)).unwrap();
}

#[test]
fn test_default_bottom_margin() {
let config = Config::default();
assert_eq!(config.editor.bottom_margin, BottomMargin::Disabled);
}

#[test]
fn test_parse_bottom_margin_fixed() {
let toml_str = r#"
[editor]
bottom_margin = { fixed = 10 }
"#;
let config: Config = toml::from_str(toml_str).unwrap();
assert_eq!(config.editor.bottom_margin, BottomMargin::Fixed(10));
}

#[test]
fn test_parse_bottom_margin_proportional() {
let toml_str = r#"
[editor]
bottom_margin = { proportional = 0.5 }
"#;
let config: Config = toml::from_str(toml_str).unwrap();
assert_eq!(config.editor.bottom_margin, BottomMargin::Proportional(0.5));
}

#[test]
fn test_parse_bottom_margin_disabled() {
let toml_str = r#"
[editor]
bottom_margin = "disabled"
"#;
let config: Config = toml::from_str(toml_str).unwrap();
assert_eq!(config.editor.bottom_margin, BottomMargin::Disabled);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ expression: schema
"default": {
"auto_match": true,
"auto_suggestions": "all",
"bottom_margin": "disabled",
"highlight_matching_bracket": false,
"key_map": {
"Alt-Hyphen": " <- ",
Expand Down Expand Up @@ -175,6 +176,50 @@ expression: schema
}
]
},
"BottomMargin": {
"description": "Bottom margin configuration to keep prompt away from terminal bottom.\n\nControls how much space to reserve at the bottom of the terminal.",
"default": "disabled",
"oneOf": [
{
"description": "Disabled (default) - no bottom margin",
"type": "string",
"enum": [
"disabled"
]
},
{
"description": "Fixed number of lines to reserve at bottom",
"type": "object",
"properties": {
"fixed": {
"description": "Number of lines to reserve at bottom (0 to terminal height)",
"type": "integer",
"minimum": 0
}
},
"additionalProperties": false,
"required": [
"fixed"
]
},
{
"description": "Fraction of terminal height to reserve",
"type": "object",
"properties": {
"proportional": {
"description": "Fraction of terminal height (0.0 = top, 1.0 = bottom/disabled)",
"type": "number",
"maximum": 1.0,
"minimum": 0.0
}
},
"additionalProperties": false,
"required": [
"proportional"
]
}
]
},
"ColorsConfig": {
"description": "Color configuration for syntax highlighting and prompts. Colors can be named (e.g., 'Red', 'DarkGray'), 256-color ({ Fixed: 99 }), or RGB ({ Rgb: [255, 0, 0] }).",
"type": "object",
Expand Down Expand Up @@ -1507,6 +1552,11 @@ expression: schema
"$ref": "#/$defs/AutoSuggestions",
"default": "all"
},
"bottom_margin": {
"description": "Bottom margin to keep prompt away from terminal bottom.\n\n- `disabled`: No margin (default)\n- `{ fixed = 10 }`: Reserve 10 lines at bottom\n- `{ proportional = 0.5 }`: Reserve bottom 50% of terminal",
"$ref": "#/$defs/BottomMargin",
"default": "disabled"
},
"highlight_matching_bracket": {
"description": "Highlight matching bracket when cursor is on a bracket.",
"type": "boolean",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ mode = "emacs"
auto_match = true
highlight_matching_bracket = false
auto_suggestions = "all"
bottom_margin = "disabled"

[editor.key_map]
Alt-Hyphen = " <- "
Expand Down
Loading
Loading