mirror of https://github.com/helix-editor/helix
set separator by section
parent
dbacaaddca
commit
d1e955e38e
|
@ -79,6 +79,9 @@ The `[editor.statusline]` key takes the following sub-keys:
|
||||||
| `center` | A list of elements aligned to the middle of the statusline | `[]` |
|
| `center` | A list of elements aligned to the middle of the statusline | `[]` |
|
||||||
| `right` | A list of elements aligned to the right of the statusline | `["diagnostics", "selections", "register", "position", "file-encoding"]` |
|
| `right` | A list of elements aligned to the right of the statusline | `["diagnostics", "selections", "register", "position", "file-encoding"]` |
|
||||||
| `separator` | The character used to separate elements in the statusline | `"│"` |
|
| `separator` | The character used to separate elements in the statusline | `"│"` |
|
||||||
|
| `separator.left` | The character used to separate elements in the left statusline | `"│"` |
|
||||||
|
| `separator.center` | The character used to separate elements in the center statusline | `"│"` |
|
||||||
|
| `separator.right` | The character used to separate elements in the right statusline | `"│"` |
|
||||||
| `mode.normal` | The text shown in the `mode` element for normal mode | `"NOR"` |
|
| `mode.normal` | The text shown in the `mode` element for normal mode | `"NOR"` |
|
||||||
| `mode.insert` | The text shown in the `mode` element for insert mode | `"INS"` |
|
| `mode.insert` | The text shown in the `mode` element for insert mode | `"INS"` |
|
||||||
| `mode.select` | The text shown in the `mode` element for select mode | `"SEL"` |
|
| `mode.select` | The text shown in the `mode` element for select mode | `"SEL"` |
|
||||||
|
@ -104,7 +107,7 @@ The following statusline elements can be configured:
|
||||||
| `primary-selection-length` | The number of characters currently in primary selection |
|
| `primary-selection-length` | The number of characters currently in primary selection |
|
||||||
| `position` | The cursor position |
|
| `position` | The cursor position |
|
||||||
| `position-percentage` | The cursor position as a percentage of the total number of lines |
|
| `position-percentage` | The cursor position as a percentage of the total number of lines |
|
||||||
| `separator` | The string defined in `editor.statusline.separator` (defaults to `"│"`) |
|
| `separator` | The string defined in `editor.statusline.separator` (defaults to `"│"`) (`separator`/`separator.left`/`separator.center`/`separator.right`/) |
|
||||||
| `spacer` | Inserts a space between elements (multiple/contiguous spacers may be specified) |
|
| `spacer` | Inserts a space between elements (multiple/contiguous spacers may be specified) |
|
||||||
| `version-control` | The current branch name or detached commit hash of the opened workspace |
|
| `version-control` | The current branch name or detached commit hash of the opened workspace |
|
||||||
| `register` | The current selected register |
|
| `register` | The current selected register |
|
||||||
|
|
|
@ -15,6 +15,7 @@ use tui::buffer::Buffer as Surface;
|
||||||
use tui::text::{Span, Spans};
|
use tui::text::{Span, Spans};
|
||||||
|
|
||||||
pub struct RenderContext<'a> {
|
pub struct RenderContext<'a> {
|
||||||
|
pub position: RenderPosition,
|
||||||
pub editor: &'a Editor,
|
pub editor: &'a Editor,
|
||||||
pub doc: &'a Document,
|
pub doc: &'a Document,
|
||||||
pub view: &'a View,
|
pub view: &'a View,
|
||||||
|
@ -37,11 +38,20 @@ impl<'a> RenderContext<'a> {
|
||||||
view,
|
view,
|
||||||
focused,
|
focused,
|
||||||
spinners,
|
spinners,
|
||||||
|
position: RenderPosition::default(),
|
||||||
parts: RenderBuffer::default(),
|
parts: RenderBuffer::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub enum RenderPosition {
|
||||||
|
#[default]
|
||||||
|
Left,
|
||||||
|
Center,
|
||||||
|
Right,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct RenderBuffer<'a> {
|
pub struct RenderBuffer<'a> {
|
||||||
pub left: Spans<'a>,
|
pub left: Spans<'a>,
|
||||||
|
@ -76,7 +86,10 @@ pub fn render(context: &mut RenderContext, viewport: Rect, surface: &mut Surface
|
||||||
element_ids
|
element_ids
|
||||||
.iter()
|
.iter()
|
||||||
.map(|element_id| get_render_function(*element_id))
|
.map(|element_id| get_render_function(*element_id))
|
||||||
.for_each(|render| render(context, write_left));
|
.for_each(|render| {
|
||||||
|
context.position = RenderPosition::Left;
|
||||||
|
render(context, write_left)
|
||||||
|
});
|
||||||
|
|
||||||
surface.set_spans(
|
surface.set_spans(
|
||||||
viewport.x,
|
viewport.x,
|
||||||
|
@ -91,7 +104,10 @@ pub fn render(context: &mut RenderContext, viewport: Rect, surface: &mut Surface
|
||||||
element_ids
|
element_ids
|
||||||
.iter()
|
.iter()
|
||||||
.map(|element_id| get_render_function(*element_id))
|
.map(|element_id| get_render_function(*element_id))
|
||||||
.for_each(|render| render(context, write_right));
|
.for_each(|render| {
|
||||||
|
context.position = RenderPosition::Right;
|
||||||
|
render(context, write_right)
|
||||||
|
});
|
||||||
|
|
||||||
surface.set_spans(
|
surface.set_spans(
|
||||||
viewport.x
|
viewport.x
|
||||||
|
@ -109,7 +125,10 @@ pub fn render(context: &mut RenderContext, viewport: Rect, surface: &mut Surface
|
||||||
element_ids
|
element_ids
|
||||||
.iter()
|
.iter()
|
||||||
.map(|element_id| get_render_function(*element_id))
|
.map(|element_id| get_render_function(*element_id))
|
||||||
.for_each(|render| render(context, write_center));
|
.for_each(|render| {
|
||||||
|
context.position = RenderPosition::Center;
|
||||||
|
render(context, write_center)
|
||||||
|
});
|
||||||
|
|
||||||
// Width of the empty space between the left and center area and between the center and right area.
|
// Width of the empty space between the left and center area and between the center and right area.
|
||||||
let spacing = 1u16;
|
let spacing = 1u16;
|
||||||
|
@ -495,6 +514,11 @@ where
|
||||||
F: Fn(&mut RenderContext, String, Option<Style>) + Copy,
|
F: Fn(&mut RenderContext, String, Option<Style>) + Copy,
|
||||||
{
|
{
|
||||||
let sep = &context.editor.config().statusline.separator;
|
let sep = &context.editor.config().statusline.separator;
|
||||||
|
let sep = match context.position {
|
||||||
|
RenderPosition::Left => &sep.left,
|
||||||
|
RenderPosition::Center => &sep.center,
|
||||||
|
RenderPosition::Right => &sep.right,
|
||||||
|
};
|
||||||
|
|
||||||
write(
|
write(
|
||||||
context,
|
context,
|
||||||
|
|
|
@ -460,7 +460,7 @@ pub struct StatusLineConfig {
|
||||||
pub left: Vec<StatusLineElement>,
|
pub left: Vec<StatusLineElement>,
|
||||||
pub center: Vec<StatusLineElement>,
|
pub center: Vec<StatusLineElement>,
|
||||||
pub right: Vec<StatusLineElement>,
|
pub right: Vec<StatusLineElement>,
|
||||||
pub separator: String,
|
pub separator: StatusLineSeparator,
|
||||||
pub mode: ModeConfig,
|
pub mode: ModeConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -484,7 +484,7 @@ impl Default for StatusLineConfig {
|
||||||
E::Position,
|
E::Position,
|
||||||
E::FileEncoding,
|
E::FileEncoding,
|
||||||
],
|
],
|
||||||
separator: String::from("│"),
|
separator: StatusLineSeparator::default(),
|
||||||
mode: ModeConfig::default(),
|
mode: ModeConfig::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -575,6 +575,135 @@ pub enum StatusLineElement {
|
||||||
Register,
|
Register,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||||
|
#[serde(rename_all = "kebab-case", default)]
|
||||||
|
pub struct StatusLineSeparator {
|
||||||
|
pub left: String,
|
||||||
|
pub center: String,
|
||||||
|
pub right: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<String> for StatusLineSeparator {
|
||||||
|
fn from(v: String) -> Self {
|
||||||
|
Self {
|
||||||
|
left: v.clone(),
|
||||||
|
center: v.clone(),
|
||||||
|
right: v,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for StatusLineSeparator {
|
||||||
|
fn default() -> Self {
|
||||||
|
String::from("│").into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum StatusLineSeparatorField {
|
||||||
|
Left,
|
||||||
|
Center,
|
||||||
|
Right,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct StatusLineSeparatorVisitor;
|
||||||
|
|
||||||
|
impl<'de> serde::de::Visitor<'de> for StatusLineSeparatorVisitor {
|
||||||
|
type Value = StatusLineSeparator;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
formatter.write_str("a string or a map with keys 'left', 'center', and 'right'")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
|
||||||
|
where
|
||||||
|
E: serde::de::Error,
|
||||||
|
{
|
||||||
|
Ok(StatusLineSeparator::from(value.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
|
||||||
|
where
|
||||||
|
M: serde::de::MapAccess<'de>,
|
||||||
|
{
|
||||||
|
let mut left = None;
|
||||||
|
let mut center = None;
|
||||||
|
let mut right = None;
|
||||||
|
|
||||||
|
while let Some(key) = map.next_key()? {
|
||||||
|
match key {
|
||||||
|
StatusLineSeparatorField::Left => {
|
||||||
|
if left.is_some() {
|
||||||
|
return Err(serde::de::Error::duplicate_field("left"));
|
||||||
|
}
|
||||||
|
left = Some(map.next_value()?);
|
||||||
|
}
|
||||||
|
StatusLineSeparatorField::Center => {
|
||||||
|
if center.is_some() {
|
||||||
|
return Err(serde::de::Error::duplicate_field("center"));
|
||||||
|
}
|
||||||
|
center = Some(map.next_value()?);
|
||||||
|
}
|
||||||
|
StatusLineSeparatorField::Right => {
|
||||||
|
if right.is_some() {
|
||||||
|
return Err(serde::de::Error::duplicate_field("right"));
|
||||||
|
}
|
||||||
|
right = Some(map.next_value()?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let left = left.unwrap_or_else(|| String::from("│"));
|
||||||
|
let center = center.unwrap_or_else(|| String::from("│"));
|
||||||
|
let right = right.unwrap_or_else(|| String::from("│"));
|
||||||
|
|
||||||
|
Ok(StatusLineSeparator {
|
||||||
|
left,
|
||||||
|
center,
|
||||||
|
right,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for StatusLineSeparatorField {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<StatusLineSeparatorField, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
struct FieldVisitor;
|
||||||
|
|
||||||
|
impl<'de> serde::de::Visitor<'de> for FieldVisitor {
|
||||||
|
type Value = StatusLineSeparatorField;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
formatter.write_str("`left`, `center`, or `right`")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_str<E>(self, value: &str) -> Result<StatusLineSeparatorField, E>
|
||||||
|
where
|
||||||
|
E: serde::de::Error,
|
||||||
|
{
|
||||||
|
match value {
|
||||||
|
"left" => Ok(StatusLineSeparatorField::Left),
|
||||||
|
"center" => Ok(StatusLineSeparatorField::Center),
|
||||||
|
"right" => Ok(StatusLineSeparatorField::Right),
|
||||||
|
_ => Err(serde::de::Error::unknown_field(value, &["left", "center", "right"])),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deserializer.deserialize_identifier(FieldVisitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for StatusLineSeparator {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
deserializer.deserialize_any(StatusLineSeparatorVisitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Cursor shape is read and used on every rendered frame and so needs
|
// Cursor shape is read and used on every rendered frame and so needs
|
||||||
// to be fast. Therefore we avoid a hashmap and use an enum indexed array.
|
// to be fast. Therefore we avoid a hashmap and use an enum indexed array.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
|
Loading…
Reference in New Issue