feat: handle icons in statusline widget, bufferline and gutter

pull/11/head
LazyTanuki 1 year ago
parent 18945587ff
commit d9e342796e

@ -531,8 +531,24 @@ impl EditorView {
let mut x = viewport.x;
let current_doc = view!(editor).doc;
let config = editor.config();
let icons_enabled = config.icons.bufferline;
for doc in editor.documents() {
let filetype_icon = doc
.language_config()
.and_then(|config| {
config
.file_types
.iter()
.map(|filetype| match filetype {
helix_core::syntax::FileType::Extension(s) => s,
helix_core::syntax::FileType::Suffix(s) => s,
})
.find_map(|filetype| editor.icons.icon_from_filetype(filetype))
})
.or_else(|| editor.icons.icon_from_path(doc.path()));
let fname = doc
.path()
.unwrap_or(&scratch)
@ -551,6 +567,22 @@ impl EditorView {
let used_width = viewport.x.saturating_sub(x);
let rem_width = surface.area.width.saturating_sub(used_width);
if icons_enabled {
if let Some(icon) = filetype_icon {
x = surface
.set_stringn(
x,
viewport.y,
format!(" {}", icon.icon_char),
rem_width as usize,
match icon.style {
Some(s) => style.patch(s.into()),
None => style,
},
)
.0;
}
}
x = surface
.set_stringn(x, viewport.y, text, rem_width as usize, style)
.0;

@ -4,6 +4,7 @@ use helix_view::document::DEFAULT_LANGUAGE_NAME;
use helix_view::{
document::{Mode, SCRATCH_BUFFER_NAME},
graphics::Rect,
icons::Icon,
theme::Style,
Document, Editor, View,
};
@ -21,6 +22,7 @@ pub struct RenderContext<'a> {
pub focused: bool,
pub spinners: &'a ProgressSpinners,
pub parts: RenderBuffer<'a>,
pub icons: RenderContextIcons<'a>,
}
impl<'a> RenderContext<'a> {
@ -31,6 +33,25 @@ impl<'a> RenderContext<'a> {
focused: bool,
spinners: &'a ProgressSpinners,
) -> Self {
// Determine icon based on language name if possible
let mut filetype_icon = None;
if let Some(language_config) = doc.language_config() {
for filetype in &language_config.file_types {
let filetype_str = match filetype {
helix_core::syntax::FileType::Extension(s) => s,
helix_core::syntax::FileType::Suffix(s) => s,
};
filetype_icon = editor.icons.icon_from_filetype(filetype_str);
if filetype_icon.is_some() {
break;
}
}
}
// Otherwise based on filetype
if filetype_icon.is_none() {
filetype_icon = editor.icons.icon_from_path(doc.path())
}
RenderContext {
editor,
doc,
@ -38,10 +59,21 @@ impl<'a> RenderContext<'a> {
focused,
spinners,
parts: RenderBuffer::default(),
icons: RenderContextIcons {
enabled: editor.config().icons.statusline,
filetype_icon,
vcs_icon: editor.icons.ui.as_ref().and_then(|ui| ui.get("vcs_branch")),
},
}
}
}
pub struct RenderContextIcons<'a> {
pub enabled: bool,
pub filetype_icon: Option<&'a Icon>,
pub vcs_icon: Option<&'a Icon>,
}
#[derive(Default)]
pub struct RenderBuffer<'a> {
pub left: Spans<'a>,
@ -148,6 +180,7 @@ where
helix_view::editor::StatusLineElement::FileEncoding => render_file_encoding,
helix_view::editor::StatusLineElement::FileLineEnding => render_file_line_ending,
helix_view::editor::StatusLineElement::FileType => render_file_type,
helix_view::editor::StatusLineElement::FileTypeIcon => render_file_type_icon,
helix_view::editor::StatusLineElement::Diagnostics => render_diagnostics,
helix_view::editor::StatusLineElement::WorkspaceDiagnostics => render_workspace_diagnostics,
helix_view::editor::StatusLineElement::Selections => render_selections,
@ -240,7 +273,13 @@ where
if warnings > 0 {
write(
context,
"●".to_string(),
context
.editor
.icons
.diagnostic
.warning
.icon_char
.to_string(),
Some(context.editor.theme.get("warning")),
);
write(context, format!(" {} ", warnings), None);
@ -249,7 +288,7 @@ where
if errors > 0 {
write(
context,
"●".to_string(),
context.editor.icons.diagnostic.error.icon_char.to_string(),
Some(context.editor.theme.get("error")),
);
write(context, format!(" {} ", errors), None);
@ -282,7 +321,13 @@ where
if warnings > 0 {
write(
context,
"●".to_string(),
context
.editor
.icons
.diagnostic
.warning
.icon_char
.to_string(),
Some(context.editor.theme.get("warning")),
);
write(context, format!(" {} ", warnings), None);
@ -291,7 +336,7 @@ where
if errors > 0 {
write(
context,
"●".to_string(),
context.editor.icons.diagnostic.error.icon_char.to_string(),
Some(context.editor.theme.get("error")),
);
write(context, format!(" {} ", errors), None);
@ -412,6 +457,21 @@ where
write(context, format!(" {} ", file_type), None);
}
fn render_file_type_icon<F>(context: &mut RenderContext, write: F)
where
F: Fn(&mut RenderContext, String, Option<Style>) + Copy,
{
if context.icons.enabled {
if let Some(icon) = context.icons.filetype_icon {
write(
context,
format!("{}", icon.icon_char),
icon.style.map(|icons_style| icons_style.into()),
)
}
}
}
fn render_file_name<F>(context: &mut RenderContext, write: F)
where
F: Fn(&mut RenderContext, String, Option<Style>) + Copy,
@ -482,11 +542,12 @@ fn render_version_control<F>(context: &mut RenderContext, write: F)
where
F: Fn(&mut RenderContext, String, Option<Style>) + Copy,
{
let head = context
.doc
.version_control_head()
.unwrap_or_default()
.to_string();
let head = context.doc.version_control_head().unwrap_or_default();
write(context, head, None);
if !head.is_empty() && context.icons.enabled {
if let Some(vcs_icon) = context.icons.vcs_icon {
return write(context, format!("{} {head}", vcs_icon.icon_char), None);
}
}
write(context, head.to_string(), None);
}

@ -476,6 +476,9 @@ pub enum StatusLineElement {
/// The file type (language ID or "text")
FileType,
/// The file type icon (from file path)
FileTypeIcon,
/// A summary of the number of errors and warnings
Diagnostics,

@ -45,7 +45,7 @@ impl GutterType {
}
pub fn diagnostic<'doc>(
_editor: &'doc Editor,
editor: &'doc Editor,
doc: &'doc Document,
_view: &View,
theme: &Theme,
@ -76,7 +76,13 @@ pub fn diagnostic<'doc>(
// This unwrap is safe because the iterator cannot be empty as it contains at least the item found by the binary search.
let diagnostic = diagnostics_on_line.max_by_key(|d| d.severity).unwrap();
write!(out, "●").unwrap();
let diagnostic_icon = match diagnostic.severity {
Some(Severity::Error) => &editor.icons.diagnostic.error,
Some(Severity::Warning) | None => &editor.icons.diagnostic.warning,
Some(Severity::Info) => &editor.icons.diagnostic.info,
Some(Severity::Hint) => &editor.icons.diagnostic.hint,
};
write!(out, "{}", diagnostic_icon.icon_char).unwrap();
return Some(match diagnostic.severity {
Some(Severity::Error) => error,
Some(Severity::Warning) | None => warning,
@ -90,19 +96,20 @@ pub fn diagnostic<'doc>(
}
pub fn diff<'doc>(
_editor: &'doc Editor,
editor: &'doc Editor,
doc: &'doc Document,
_view: &View,
theme: &Theme,
_is_focused: bool,
) -> GutterFn<'doc> {
let added = theme.get("diff.plus");
let deleted = theme.get("diff.minus");
let modified = theme.get("diff.delta");
if let Some(diff_handle) = doc.diff_handle() {
let added = theme.get("diff.plus");
let deleted = theme.get("diff.minus");
let modified = theme.get("diff.delta");
let hunks = diff_handle.load();
let mut hunk_i = 0;
let mut hunk = hunks.nth_hunk(hunk_i);
let icons = &editor.icons;
Box::new(
move |line: usize, _selected: bool, first_visual_line: bool, out: &mut String| {
// truncating the line is fine here because we don't compute diffs
@ -122,18 +129,18 @@ pub fn diff<'doc>(
}
let (icon, style) = if hunk.is_pure_insertion() {
("▍", added)
(&icons.diff.added, added)
} else if hunk.is_pure_removal() {
if !first_visual_line {
return None;
}
("▔", deleted)
(&icons.diff.deleted, deleted)
} else {
("▍", modified)
(&icons.diff.modified, modified)
};
write!(out, "{}", icon).unwrap();
Some(style)
write!(out, "{}", icon.icon_char).unwrap();
icon.style.map(|i| i.into()).or(Some(style))
},
)
} else {
@ -275,7 +282,11 @@ pub fn breakpoints<'doc>(
breakpoint_style
};
let sym = if breakpoint.verified { "●" } else { "◯" };
let sym = if breakpoint.verified {
editor.icons.breakpoint.verified.icon_char
} else {
editor.icons.breakpoint.unverified.icon_char
};
write!(out, "{}", sym).unwrap();
Some(style)
},
@ -310,7 +321,7 @@ fn execution_pause_indicator<'doc>(
return None;
}
let sym = "▶";
let sym = editor.icons.breakpoint.pause_indicator.icon_char;
write!(out, "{}", sym).unwrap();
Some(style)
},

@ -156,6 +156,7 @@ pub struct Diagnostic {
pub struct Breakpoint {
pub verified: Icon,
pub unverified: Icon,
pub pause_indicator: Icon,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]

@ -9,8 +9,9 @@ info = {icon = "●"}
hint = {icon = "●"}
[breakpoint]
verified = {icon = "▲"}
unverified = {icon = "⊚"}
verified = {icon = "●"}
unverified = {icon = "◯"}
pause-indicator = {icon = "▶"}
[diff]
added = {icon = "▍"}

@ -7,8 +7,9 @@ info = {icon = ""}
hint = {icon = ""}
[breakpoint]
verified = {icon = "▲"}
unverified = {icon = "⊚"}
verified = {icon = "●"}
unverified = {icon = "◯"}
pause-indicator = {icon = "▶"}
[diff]
added = {icon = "▍"}
@ -47,7 +48,7 @@ type-parameter = {icon = ""}
file = {icon = ""}
folder = {icon = ""}
folder_opened = {icon = ""}
git_branch = {icon = ""}
vcs_branch = {icon = ""}
[mime-type]
# This is heavily based on https://github.com/nvim-tree/nvim-web-devicons

Loading…
Cancel
Save