Compare commits

..

2 Commits

Author SHA1 Message Date
RoloEdits 5ce27b0087
Merge 70deab1b19 into 4281228da3 2025-07-24 14:36:52 -03:00
Rolo 70deab1b19 feat: add support for basic icons 2025-07-23 03:18:31 -07:00
11 changed files with 176 additions and 237 deletions

View File

@ -3215,9 +3215,9 @@ fn buffer_picker(cx: &mut Context) {
spans.push(icon.to_span_with(|icon| format!("{icon} "))); spans.push(icon.to_span_with(|icon| format!("{icon} ")));
} }
spans.push(Span::raw(name.to_string())); spans.push(name.to_string().into());
Cell::from(Spans::from(spans)) Spans::from(spans).into()
}), }),
]; ];
let picker = Picker::new(columns, 2, items, (), |cx, meta, action| { let picker = Picker::new(columns, 2, items, (), |cx, meta, action| {
@ -3281,7 +3281,6 @@ fn jumplist_picker(cx: &mut Context) {
.as_deref() .as_deref()
.and_then(Path::to_str) .and_then(Path::to_str)
.unwrap_or(SCRATCH_BUFFER_NAME); .unwrap_or(SCRATCH_BUFFER_NAME);
let icons = ICONS.load(); let icons = ICONS.load();
let mut spans = Vec::with_capacity(2); let mut spans = Vec::with_capacity(2);
@ -3290,9 +3289,9 @@ fn jumplist_picker(cx: &mut Context) {
spans.push(icon.to_span_with(|icon| format!("{icon} "))); spans.push(icon.to_span_with(|icon| format!("{icon} ")));
} }
spans.push(Span::raw(name.to_string())); spans.push(name.to_string().into());
Cell::from(Spans::from(spans)) Spans::from(spans).into()
}), }),
ui::PickerColumn::new("flags", |item: &JumpMeta, _| { ui::PickerColumn::new("flags", |item: &JumpMeta, _| {
let mut flags = Vec::new(); let mut flags = Vec::new();

View File

@ -247,7 +247,6 @@ fn diag_picker(
"severity", "severity",
|item: &PickerDiagnostic, styles: &DiagnosticStyles| { |item: &PickerDiagnostic, styles: &DiagnosticStyles| {
let icons = ICONS.load(); let icons = ICONS.load();
match item.diag.severity { match item.diag.severity {
Some(DiagnosticSeverity::HINT) => { Some(DiagnosticSeverity::HINT) => {
Span::styled(format!("{} HINT", icons.diagnostic().hint()), styles.hint) Span::styled(format!("{} HINT", icons.diagnostic().hint()), styles.hint)
@ -416,9 +415,8 @@ pub fn symbol_picker(cx: &mut Context) {
let call = move |_editor: &mut Editor, compositor: &mut Compositor| { let call = move |_editor: &mut Editor, compositor: &mut Compositor| {
let columns = [ let columns = [
ui::PickerColumn::new("kind", |item: &SymbolInformationItem, _| { ui::PickerColumn::new("kind", |item: &SymbolInformationItem, _| {
let name = display_symbol_kind(item.symbol.kind);
let icons = ICONS.load(); let icons = ICONS.load();
let name = display_symbol_kind(item.symbol.kind);
if let Some(icon) = icons.kind().get(name) { if let Some(icon) = icons.kind().get(name) {
icon.to_span_with(|icon| format!("{icon} {name}")).into() icon.to_span_with(|icon| format!("{icon} {name}")).into()

View File

@ -107,8 +107,8 @@ impl menu::Item for CompletionItem {
}; };
let icons = ICONS.load(); let icons = ICONS.load();
let name = &kind.0[0].content; let name = &kind.0[0].content;
let is_folder = kind.0[0].content == "folder"; let is_folder = kind.0[0].content == "folder";
if let Some(icon) = icons.kind().get(name) { if let Some(icon) = icons.kind().get(name) {

View File

@ -9,7 +9,6 @@ use helix_core::{visual_offset_from_block, Position, RopeSlice};
use helix_stdx::rope::RopeSliceExt; use helix_stdx::rope::RopeSliceExt;
use helix_view::editor::{WhitespaceConfig, WhitespaceRenderValue}; use helix_view::editor::{WhitespaceConfig, WhitespaceRenderValue};
use helix_view::graphics::Rect; use helix_view::graphics::Rect;
use helix_view::icons::ICONS;
use helix_view::theme::Style; use helix_view::theme::Style;
use helix_view::view::ViewPosition; use helix_view::view::ViewPosition;
use helix_view::{Document, Theme}; use helix_view::{Document, Theme};
@ -207,41 +206,38 @@ impl<'a> TextRenderer<'a> {
viewport: Rect, viewport: Rect,
) -> TextRenderer<'a> { ) -> TextRenderer<'a> {
let editor_config = doc.config.load(); let editor_config = doc.config.load();
let WhitespaceConfig {
let WhitespaceConfig { render } = &editor_config.whitespace; render: ws_render,
characters: ws_chars,
} = &editor_config.whitespace;
let tab_width = doc.tab_width(); let tab_width = doc.tab_width();
let tab = if ws_render.tab() == WhitespaceRenderValue::All {
let icons = ICONS.load(); std::iter::once(ws_chars.tab)
.chain(std::iter::repeat(ws_chars.tabpad).take(tab_width - 1))
let whitespace = icons.ui().r#virtual();
let tab = if render.tab() == WhitespaceRenderValue::All {
std::iter::once(whitespace.tab())
.chain(std::iter::repeat(whitespace.tabpad()).take(tab_width - 1))
.collect() .collect()
} else { } else {
" ".repeat(tab_width) " ".repeat(tab_width)
}; };
let virtual_tab = " ".repeat(tab_width); let virtual_tab = " ".repeat(tab_width);
let newline = if render.newline() == WhitespaceRenderValue::All { let newline = if ws_render.newline() == WhitespaceRenderValue::All {
whitespace.newline().into() ws_chars.newline.into()
} else { } else {
" ".to_owned() " ".to_owned()
}; };
let space = if render.space() == WhitespaceRenderValue::All { let space = if ws_render.space() == WhitespaceRenderValue::All {
whitespace.space().into() ws_chars.space.into()
} else { } else {
" ".to_owned() " ".to_owned()
}; };
let nbsp = if render.nbsp() == WhitespaceRenderValue::All { let nbsp = if ws_render.nbsp() == WhitespaceRenderValue::All {
whitespace.nbsp().into() ws_chars.nbsp.into()
} else { } else {
" ".to_owned() " ".to_owned()
}; };
let nnbsp = if render.nnbsp() == WhitespaceRenderValue::All { let nnbsp = if ws_render.nnbsp() == WhitespaceRenderValue::All {
whitespace.nnbsp().into() ws_chars.nnbsp.into()
} else { } else {
" ".to_owned() " ".to_owned()
}; };
@ -275,7 +271,6 @@ impl<'a> TextRenderer<'a> {
offset, offset,
} }
} }
/// Draws a single `grapheme` at the current render position with a specified `style`. /// Draws a single `grapheme` at the current render position with a specified `style`.
pub fn draw_decoration_grapheme( pub fn draw_decoration_grapheme(
&mut self, &mut self,

View File

@ -235,8 +235,7 @@ impl EditorView {
theme: &Theme, theme: &Theme,
) { ) {
let editor_rulers = &editor.config().rulers; let editor_rulers = &editor.config().rulers;
let ruler_theme = theme
let style = theme
.try_get("ui.virtual.ruler") .try_get("ui.virtual.ruler")
.unwrap_or_else(|| Style::default().bg(Color::Red)); .unwrap_or_else(|| Style::default().bg(Color::Red));
@ -254,12 +253,7 @@ impl EditorView {
.filter_map(|ruler| ruler.checked_sub(1 + view_offset.horizontal_offset as u16)) .filter_map(|ruler| ruler.checked_sub(1 + view_offset.horizontal_offset as u16))
.filter(|ruler| ruler < &viewport.width) .filter(|ruler| ruler < &viewport.width)
.map(|ruler| viewport.clip_left(ruler).with_width(1)) .map(|ruler| viewport.clip_left(ruler).with_width(1))
.for_each(|area| { .for_each(|area| surface.set_style(area, ruler_theme))
let icons = ICONS.load();
for y in area.y..area.height {
surface.set_string(area.x, y, icons.ui().r#virtual().ruler(), style);
}
});
} }
fn viewport_byte_range( fn viewport_byte_range(
@ -589,7 +583,7 @@ impl EditorView {
let mut x = viewport.x; let mut x = viewport.x;
let current_doc = view!(editor).doc; let current_doc = view!(editor).doc;
for doc in editor.documents() { for (idx, doc) in editor.documents().enumerate() {
let fname = doc let fname = doc
.path() .path()
.unwrap_or(&scratch) .unwrap_or(&scratch)
@ -604,9 +598,24 @@ impl EditorView {
bufferline_inactive bufferline_inactive
}; };
let lang = doc.language_name().unwrap_or(DEFAULT_LANGUAGE_NAME);
let icons = ICONS.load(); let icons = ICONS.load();
let lang = doc.language_name().unwrap_or(DEFAULT_LANGUAGE_NAME); // Render the separator before the text if the current document is not first.
if idx > 0 {
let used_width = viewport.x.saturating_sub(x);
let rem_width = surface.area.width.saturating_sub(used_width);
x = surface
.set_stringn(
x,
viewport.y,
icons.ui().bufferline().separator(),
rem_width as usize,
bufferline_inactive,
)
.0;
}
if let Some(icon) = icons if let Some(icon) = icons
.fs() .fs()

View File

@ -32,7 +32,6 @@ pub use text::Text;
use helix_view::Editor; use helix_view::Editor;
use tui::text::{Span, Spans, ToSpan}; use tui::text::{Span, Spans, ToSpan};
use tui::widgets::Cell;
use std::path::Path; use std::path::Path;
use std::{error::Error, path::PathBuf}; use std::{error::Error, path::PathBuf};
@ -334,7 +333,7 @@ pub fn file_explorer(root: PathBuf, editor: &Editor) -> Result<FileExplorer, std
spans.push(icon.to_span_with(|icon| format!("{icon} "))); spans.push(icon.to_span_with(|icon| format!("{icon} ")));
spans.push(Span::raw(name)); spans.push(Span::raw(name));
Cell::from(Spans::from(spans)) Spans::from(spans).into()
} else { } else {
name.into() name.into()
} }

View File

@ -236,7 +236,6 @@ where
}); });
let icons = ICONS.load(); let icons = ICONS.load();
for sev in &context.editor.config().statusline.diagnostics { for sev in &context.editor.config().statusline.diagnostics {
match sev { match sev {
Severity::Hint if hints > 0 => { Severity::Hint if hints > 0 => {
@ -487,10 +486,10 @@ fn render_file_type<'a, F>(context: &mut RenderContext<'a>, write: F)
where where
F: Fn(&mut RenderContext<'a>, Span<'a>) + Copy, F: Fn(&mut RenderContext<'a>, Span<'a>) + Copy,
{ {
let icons = ICONS.load();
let lang = context.doc.language_name().unwrap_or(DEFAULT_LANGUAGE_NAME); let lang = context.doc.language_name().unwrap_or(DEFAULT_LANGUAGE_NAME);
let icons = ICONS.load();
if let Some(icon) = icons if let Some(icon) = icons
.fs() .fs()
.from_optional_path_or_lang(context.doc.path().map(|path| path.as_path()), lang) .from_optional_path_or_lang(context.doc.path().map(|path| path.as_path()), lang)
@ -578,13 +577,10 @@ fn render_separator<'a, F>(context: &mut RenderContext<'a>, write: F)
where where
F: Fn(&mut RenderContext<'a>, Span<'a>) + Copy, F: Fn(&mut RenderContext<'a>, Span<'a>) + Copy,
{ {
let sep = &context.editor.config().statusline.separator;
let style = context.editor.theme.get("ui.statusline.separator"); let style = context.editor.theme.get("ui.statusline.separator");
let icons = ICONS.load(); write(context, Span::styled(sep.to_string(), style));
let separator = icons.ui().statusline().separator().to_string();
write(context, Span::styled(separator, style));
} }
fn render_spacer<'a, F>(context: &mut RenderContext<'a>, write: F) fn render_spacer<'a, F>(context: &mut RenderContext<'a>, write: F)

View File

@ -43,7 +43,6 @@ use helix_core::{
ChangeSet, Diagnostic, LineEnding, Range, Rope, RopeBuilder, Selection, Syntax, Transaction, ChangeSet, Diagnostic, LineEnding, Range, Rope, RopeBuilder, Selection, Syntax, Transaction,
}; };
use crate::icons::{Icons, ICONS};
use crate::{ use crate::{
editor::Config, editor::Config,
events::{DocumentDidChange, SelectionDidChange}, events::{DocumentDidChange, SelectionDidChange},
@ -2244,10 +2243,8 @@ impl Document {
.unwrap_or(40); .unwrap_or(40);
let wrap_indicator = language_soft_wrap let wrap_indicator = language_soft_wrap
.and_then(|soft_wrap| soft_wrap.wrap_indicator.clone()) .and_then(|soft_wrap| soft_wrap.wrap_indicator.clone())
.unwrap_or_else(|| { .or_else(|| config.soft_wrap.wrap_indicator.clone())
let icons: arc_swap::access::DynGuard<Icons> = ICONS.load(); .unwrap_or_else(|| "".into());
icons.ui().r#virtual().wrap().to_string()
});
let tab_width = self.tab_width() as u16; let tab_width = self.tab_width() as u16;
TextFormat { TextFormat {
soft_wrap: enable_soft_wrap && viewport_width > 10, soft_wrap: enable_soft_wrap && viewport_width > 10,

View File

@ -508,6 +508,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 mode: ModeConfig, pub mode: ModeConfig,
pub diagnostics: Vec<Severity>, pub diagnostics: Vec<Severity>,
pub workspace_diagnostics: Vec<Severity>, pub workspace_diagnostics: Vec<Severity>,
@ -533,6 +534,7 @@ impl Default for StatusLineConfig {
E::Position, E::Position,
E::FileEncoding, E::FileEncoding,
], ],
separator: String::from(""),
mode: ModeConfig::default(), mode: ModeConfig::default(),
diagnostics: vec![Severity::Warning, Severity::Error], diagnostics: vec![Severity::Warning, Severity::Error],
workspace_diagnostics: vec![Severity::Warning, Severity::Error], workspace_diagnostics: vec![Severity::Warning, Severity::Error],
@ -751,12 +753,14 @@ impl std::str::FromStr for GutterType {
#[serde(default)] #[serde(default)]
pub struct WhitespaceConfig { pub struct WhitespaceConfig {
pub render: WhitespaceRender, pub render: WhitespaceRender,
pub characters: WhitespaceCharacters,
} }
impl Default for WhitespaceConfig { impl Default for WhitespaceConfig {
fn default() -> Self { fn default() -> Self {
Self { Self {
render: WhitespaceRender::Basic(WhitespaceRenderValue::None), render: WhitespaceRender::Basic(WhitespaceRenderValue::None),
characters: WhitespaceCharacters::default(),
} }
} }
} }
@ -882,6 +886,30 @@ where
} }
} }
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(default)]
pub struct WhitespaceCharacters {
pub space: char,
pub nbsp: char,
pub nnbsp: char,
pub tab: char,
pub tabpad: char,
pub newline: char,
}
impl Default for WhitespaceCharacters {
fn default() -> Self {
Self {
space: '·', // U+00B7
nbsp: '', // U+237D
nnbsp: '', // U+2423
tab: '', // U+2192
newline: '', // U+23CE
tabpad: ' ',
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(default, rename_all = "kebab-case")] #[serde(default, rename_all = "kebab-case")]
pub struct IndentGuidesConfig { pub struct IndentGuidesConfig {

View File

@ -127,18 +127,17 @@ pub fn diff<'doc>(
let icons = ICONS.load(); let icons = ICONS.load();
let (icon, style) = if hunk.is_pure_insertion() { let (icon, style) = if hunk.is_pure_insertion() {
(icons.ui().gutter().added(), added) (icons.gutter().added(), added)
} else if hunk.is_pure_removal() { } else if hunk.is_pure_removal() {
if !first_visual_line { if !first_visual_line {
return None; return None;
} }
(icons.ui().gutter().removed(), deleted) (icons.gutter().removed(), deleted)
} else { } else {
(icons.ui().gutter().modified(), modified) (icons.gutter().modified(), modified)
}; };
write!(out, "{icon}").unwrap(); write!(out, "{}", icon).unwrap();
Some(style) Some(style)
}, },
) )

View File

@ -20,6 +20,7 @@ pub struct Icons {
diagnostic: Diagnostic, diagnostic: Diagnostic,
vcs: Vcs, vcs: Vcs,
dap: Dap, dap: Dap,
gutter: Gutter,
ui: Ui, ui: Ui,
} }
@ -49,6 +50,11 @@ impl Icons {
&self.dap &self.dap
} }
#[inline]
pub fn gutter(&self) -> &Gutter {
&self.gutter
}
#[inline] #[inline]
pub fn ui(&self) -> &Ui { pub fn ui(&self) -> &Ui {
&self.ui &self.ui
@ -107,46 +113,48 @@ impl Kind {
return None; return None;
} }
match kind { let icon = match kind {
"file" => self.file(), "file" => self.file()?,
"folder" => self.folder(), "folder" => self.folder()?,
"module" => self.module(), "module" => self.module()?,
"namespace" => self.namespace(), "namespace" => self.namespace()?,
"package" => self.package(), "package" => self.package()?,
"class" => self.class(), "class" => self.class()?,
"method" => self.method(), "method" => self.method()?,
"property" => self.property(), "property" => self.property()?,
"field" => self.field(), "field" => self.field()?,
"construct" => self.constructor(), "construct" => self.constructor()?,
"enum" => self.r#enum(), "enum" => self.r#enum()?,
"interface" => self.interface(), "interface" => self.interface()?,
"function" => self.function(), "function" => self.function()?,
"variable" => self.variable(), "variable" => self.variable()?,
"constant" => self.constant(), "constant" => self.constant()?,
"string" => self.string(), "string" => self.string()?,
"number" => self.number(), "number" => self.number()?,
"boolean" => self.boolean(), "boolean" => self.boolean()?,
"array" => self.array(), "array" => self.array()?,
"object" => self.object(), "object" => self.object()?,
"key" => self.key(), "key" => self.key()?,
"null" => self.null(), "null" => self.null()?,
"enum_member" => self.enum_member(), "enum_member" => self.enum_member()?,
"struct" => self.r#struct(), "struct" => self.r#struct()?,
"event" => self.event(), "event" => self.event()?,
"operator" => self.operator(), "operator" => self.operator()?,
"typeparam" => self.type_parameter(), "typeparam" => self.type_parameter()?,
"color" => Some(self.color()), "color" => self.color(),
"keyword" => self.keyword(), "keyword" => self.keyword()?,
"value" => self.value(), "value" => self.value()?,
"snippet" => self.snippet(), "snippet" => self.snippet()?,
"reference" => self.reference(), "reference" => self.reference()?,
"text" => self.text(), "text" => self.text()?,
"unit" => self.unit(), "unit" => self.unit()?,
"word" => self.word(), "word" => self.word()?,
"spellcheck" => self.spellcheck(), "spellcheck" => self.spellcheck()?,
_ => None, _ => return None,
} };
Some(icon)
} }
#[inline] #[inline]
@ -548,23 +556,21 @@ pub struct Fs {
mime: HashMap<String, Icon>, mime: HashMap<String, Icon>,
} }
macro_rules! iconmap { macro_rules! mimes {
( $( $key:literal => { glyph: $glyph:expr $(, color: $color:expr)? } ),* $(,)? ) => {{ ( $( $key:literal => { glyph: $glyph:expr $(, color: $color:expr)? } ),* $(,)? ) => {{
HashMap::from( let mut map = HashMap::new();
[ $(
$( map.insert(String::from($key), Icon {
(String::from($key), Icon { glyph: String::from($glyph),
glyph: String::from($glyph), color: None $(.or( Some(Color::from_hex($color).unwrap())) )?,
color: None $(.or( Some(Color::from_hex($color).unwrap())) )?, });
}), )*
)* map
]
)
}}; }};
} }
static MIMES: once_cell::sync::Lazy<HashMap<String, Icon>> = once_cell::sync::Lazy::new(|| { static MIMES: once_cell::sync::Lazy<HashMap<String, Icon>> = once_cell::sync::Lazy::new(|| {
iconmap! { mimes! {
// Language name // Language name
"git-commit" => {glyph: "", color: "#f15233" }, "git-commit" => {glyph: "", color: "#f15233" },
"git-rebase" => {glyph: "", color: "#f15233" }, "git-rebase" => {glyph: "", color: "#f15233" },
@ -689,11 +695,13 @@ impl Fs {
return None; return None;
} }
if is_open { let dir = if is_open {
self.directory_open.as_deref().or(Some("󰝰")) self.directory_open.as_deref().unwrap_or("󰝰")
} else { } else {
self.directory.as_deref().or(Some("󰉋")) self.directory.as_deref().unwrap_or("󰉋")
} };
Some(dir)
} }
#[inline] #[inline]
@ -800,41 +808,6 @@ impl Dap {
} }
} }
#[derive(Debug, Deserialize, PartialEq, Eq, Clone, Default)]
#[serde(default, deny_unknown_fields)]
pub struct Ui {
workspace: Option<Icon>,
gutter: Gutter,
#[serde(rename = "virtual")]
r#virtual: Virtual,
statusline: Statusline,
}
impl Ui {
/// Returns a workspace diagnostic icon.
///
/// If no icon is set in the config, it will return `W` by default.
#[inline]
pub fn workspace(&self) -> Icon {
self.workspace.clone().unwrap_or_else(|| Icon::from("W"))
}
#[inline]
pub fn gutter(&self) -> &Gutter {
&self.gutter
}
#[inline]
pub fn r#virtual(&self) -> &Virtual {
&self.r#virtual
}
#[inline]
pub fn statusline(&self) -> &Statusline {
&self.statusline
}
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Default)] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Default)]
pub struct Gutter { pub struct Gutter {
added: Option<String>, added: Option<String>,
@ -859,100 +832,46 @@ impl Gutter {
} }
} }
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Default)]
pub struct Virtual {
// Whitespace
space: Option<String>,
nbsp: Option<String>,
nnbsp: Option<String>,
tab: Option<String>,
newline: Option<String>,
tabpad: Option<String>,
// Soft-wrap
wrap: Option<String>,
// Indentation guide
indentation: Option<String>,
// Ruler
ruler: Option<String>,
}
impl Virtual {
#[inline]
pub fn space(&self) -> &str {
// Default: U+00B7
self.space.as_deref().unwrap_or("·")
}
#[inline]
pub fn nbsp(&self) -> &str {
// Default: U+237D
self.nbsp.as_deref().unwrap_or("")
}
#[inline]
pub fn nnbsp(&self) -> &str {
// Default: U+2423
self.nnbsp.as_deref().unwrap_or("")
}
#[inline]
pub fn tab(&self) -> &str {
// Default: U+2192
self.tab.as_deref().unwrap_or("")
}
#[inline]
pub fn newline(&self) -> &str {
// Default: U+23CE
self.newline.as_deref().unwrap_or("")
}
#[inline]
pub fn tabpad(&self) -> &str {
// Default: U+23CE
self.tabpad.as_deref().unwrap_or(" ")
}
#[inline]
pub fn wrap(&self) -> &str {
// Default: U+21AA
self.wrap.as_deref().unwrap_or("")
}
#[inline]
pub fn indentation(&self) -> &str {
// Default: U+254E
self.indentation.as_deref().unwrap_or("")
}
#[inline]
pub fn ruler(&self) -> &str {
// TODO: Default: U+00A6: ¦
self.ruler.as_deref().unwrap_or(" ")
}
}
#[derive(Debug, Deserialize, PartialEq, Eq, Clone, Default)]
pub struct Statusline {
separator: Option<String>,
}
impl Statusline {
#[inline]
pub fn separator(&self) -> &str {
self.separator.as_deref().unwrap_or("")
}
}
#[derive(Debug, Default, PartialEq, Eq, Clone)] #[derive(Debug, Default, PartialEq, Eq, Clone)]
pub struct Icon { pub struct Icon {
glyph: String, glyph: String,
color: Option<Color>, color: Option<Color>,
} }
#[derive(Debug, Deserialize, PartialEq, Eq, Clone, Default)]
#[serde(default, deny_unknown_fields)]
pub struct Ui {
workspace: Option<Icon>,
bufferline: BufferLine,
}
impl Ui {
/// Returns a workspace diagnostic icon.
///
/// If no icon is set in the config, it will return `W` by default.
#[inline]
pub fn workspace(&self) -> Icon {
self.workspace.clone().unwrap_or_else(|| Icon::from("W"))
}
#[inline]
pub fn bufferline(&self) -> &BufferLine {
&self.bufferline
}
}
#[derive(Debug, Deserialize, PartialEq, Eq, Clone, Default)]
pub struct BufferLine {
separator: Option<String>,
}
impl BufferLine {
#[inline]
pub fn separator(&self) -> &str {
self.separator.as_deref().unwrap_or("")
}
}
impl Icon { impl Icon {
pub fn glyph(&self) -> &str { pub fn glyph(&self) -> &str {
self.glyph.as_str() self.glyph.as_str()