Skip to content

feat: add basic support for icons #12369

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
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
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

85 changes: 72 additions & 13 deletions helix-term/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ use helix_core::{
use helix_view::{
document::{FormatterError, Mode, SCRATCH_BUFFER_NAME},
editor::Action,
icons::ICONS,
info::Info,
input::KeyEvent,
keyboard::KeyCode,
Expand Down Expand Up @@ -3180,11 +3181,32 @@ fn buffer_picker(cx: &mut Context) {
.path
.as_deref()
.map(helix_stdx::path::get_relative_path);
path.as_deref()

let name = path
.as_deref()
.and_then(Path::to_str)
.unwrap_or(SCRATCH_BUFFER_NAME)
.to_string()
.into()
.unwrap_or(SCRATCH_BUFFER_NAME);
let icons = ICONS.load();

let mut spans = Vec::with_capacity(2);

if let Some(icon) = icons
.mime()
.get(path.as_ref().map(|path| path.to_path_buf()).as_ref(), None)
{
if let Some(color) = icon.color() {
spans.push(Span::styled(
format!("{} ", icon.glyph()),
Style::default().fg(color),
));
} else {
spans.push(Span::raw(format!("{} ", icon.glyph())));
}
}

spans.push(Span::raw(name.to_string()));

Spans::from(spans).into()
}),
];
let picker = Picker::new(columns, 2, items, (), |cx, meta, action| {
Expand Down Expand Up @@ -3243,11 +3265,32 @@ fn jumplist_picker(cx: &mut Context) {
.path
.as_deref()
.map(helix_stdx::path::get_relative_path);
path.as_deref()

let name = path
.as_deref()
.and_then(Path::to_str)
.unwrap_or(SCRATCH_BUFFER_NAME)
.to_string()
.into()
.unwrap_or(SCRATCH_BUFFER_NAME);
let icons = ICONS.load();

let mut spans = Vec::with_capacity(2);

if let Some(icon) = icons
.mime()
.get(path.as_ref().map(|path| path.to_path_buf()).as_ref(), None)
{
if let Some(color) = icon.color() {
spans.push(Span::styled(
format!("{} ", icon.glyph()),
Style::default().fg(color),
));
} else {
spans.push(Span::raw(format!("{} ", icon.glyph())));
}
}

spans.push(Span::raw(name.to_string()));

Spans::from(spans).into()
}),
ui::PickerColumn::new("flags", |item: &JumpMeta, _| {
let mut flags = Vec::new();
Expand Down Expand Up @@ -3317,12 +3360,28 @@ fn changed_file_picker(cx: &mut Context) {

let columns = [
PickerColumn::new("change", |change: &FileChange, data: &FileChangeData| {
let icons = ICONS.load();
match change {
FileChange::Untracked { .. } => Span::styled("+ untracked", data.style_untracked),
FileChange::Modified { .. } => Span::styled("~ modified", data.style_modified),
FileChange::Conflict { .. } => Span::styled("x conflict", data.style_conflict),
FileChange::Deleted { .. } => Span::styled("- deleted", data.style_deleted),
FileChange::Renamed { .. } => Span::styled("> renamed", data.style_renamed),
FileChange::Untracked { .. } => Span::styled(
format!("{} untracked", icons.vcs().added()),
data.style_untracked,
),
FileChange::Modified { .. } => Span::styled(
format!("{} modified", icons.vcs().modified()),
data.style_modified,
),
FileChange::Conflict { .. } => Span::styled(
format!("{} conflict", icons.vcs().conflict()),
data.style_conflict,
),
FileChange::Deleted { .. } => Span::styled(
format!("{} deleted", icons.vcs().removed()),
data.style_deleted,
),
FileChange::Renamed { .. } => Span::styled(
format!("{} renamed", icons.vcs().renamed()),
data.style_renamed,
),
}
.into()
}),
Expand Down
56 changes: 49 additions & 7 deletions helix-term/src/commands/lsp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use helix_view::{
document::{DocumentInlayHints, DocumentInlayHintsId},
editor::Action,
handlers::lsp::SignatureHelpInvoked,
icons::ICONS,
theme::Style,
Document, View,
};
Expand Down Expand Up @@ -182,7 +183,7 @@ fn display_symbol_kind(kind: lsp::SymbolKind) -> &'static str {
lsp::SymbolKind::OBJECT => "object",
lsp::SymbolKind::KEY => "key",
lsp::SymbolKind::NULL => "null",
lsp::SymbolKind::ENUM_MEMBER => "enummem",
lsp::SymbolKind::ENUM_MEMBER => "enum_member",
lsp::SymbolKind::STRUCT => "struct",
lsp::SymbolKind::EVENT => "event",
lsp::SymbolKind::OPERATOR => "operator",
Expand Down Expand Up @@ -242,11 +243,22 @@ fn diag_picker(
ui::PickerColumn::new(
"severity",
|item: &PickerDiagnostic, styles: &DiagnosticStyles| {
let icons = ICONS.load();
match item.diag.severity {
Some(DiagnosticSeverity::HINT) => Span::styled("HINT", styles.hint),
Some(DiagnosticSeverity::INFORMATION) => Span::styled("INFO", styles.info),
Some(DiagnosticSeverity::WARNING) => Span::styled("WARN", styles.warning),
Some(DiagnosticSeverity::ERROR) => Span::styled("ERROR", styles.error),
Some(DiagnosticSeverity::HINT) => {
Span::styled(format!("{} HINT", icons.diagnostic().hint()), styles.hint)
}
Some(DiagnosticSeverity::INFORMATION) => {
Span::styled(format!("{} INFO", icons.diagnostic().info()), styles.info)
}
Some(DiagnosticSeverity::WARNING) => Span::styled(
format!("{} WARN", icons.diagnostic().warning()),
styles.warning,
),
Some(DiagnosticSeverity::ERROR) => Span::styled(
format!("{} ERROR", icons.diagnostic().error()),
styles.error,
),
_ => Span::raw(""),
}
.into()
Expand Down Expand Up @@ -397,7 +409,22 @@ pub fn symbol_picker(cx: &mut Context) {
let call = move |_editor: &mut Editor, compositor: &mut Compositor| {
let columns = [
ui::PickerColumn::new("kind", |item: &SymbolInformationItem, _| {
display_symbol_kind(item.symbol.kind).into()
let icons = ICONS.load();
let name = display_symbol_kind(item.symbol.kind);

if let Some(icon) = icons.kind().get(name) {
if let Some(color) = icon.color() {
Span::styled(
format!("{} {name}", icon.glyph()),
Style::default().fg(color),
)
.into()
} else {
format!("{} {name}", icon.glyph()).into()
}
} else {
name.into()
}
}),
// Some symbols in the document symbol picker may have a URI that isn't
// the current file. It should be rare though, so we concatenate that
Expand Down Expand Up @@ -515,7 +542,22 @@ pub fn workspace_symbol_picker(cx: &mut Context) {
};
let columns = [
ui::PickerColumn::new("kind", |item: &SymbolInformationItem, _| {
display_symbol_kind(item.symbol.kind).into()
let icons = ICONS.load();
let name = display_symbol_kind(item.symbol.kind);

if let Some(icon) = icons.kind().get(name) {
if let Some(color) = icon.color() {
Span::styled(
format!("{} {name}", icon.glyph()),
Style::default().fg(color),
)
.into()
} else {
format!("{} {name}", icon.glyph()).into()
}
} else {
name.into()
}
}),
ui::PickerColumn::new("name", |item: &SymbolInformationItem, _| {
item.symbol.name.as_str().into()
Expand Down
24 changes: 24 additions & 0 deletions helix-term/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ use crate::keymap;
use crate::keymap::{merge_keys, KeyTrie};
use helix_loader::merge_toml_values;
use helix_view::document::Mode;
use helix_view::icons::{Icons, ICONS};
use serde::Deserialize;
use std::collections::HashMap;
use std::fmt::Display;
use std::fs;
use std::io::Error as IOError;
use std::sync::Arc;
use toml::de::Error as TomlError;

#[derive(Debug, Clone, PartialEq)]
Expand All @@ -22,6 +24,7 @@ pub struct ConfigRaw {
pub theme: Option<String>,
pub keys: Option<HashMap<Mode, KeyTrie>>,
pub editor: Option<toml::Value>,
pub icons: Option<toml::Value>,
}

impl Default for Config {
Expand Down Expand Up @@ -64,6 +67,7 @@ impl Config {
global.and_then(|file| toml::from_str(&file).map_err(ConfigLoadError::BadConfig));
let local_config: Result<ConfigRaw, ConfigLoadError> =
local.and_then(|file| toml::from_str(&file).map_err(ConfigLoadError::BadConfig));

let res = match (global_config, local_config) {
(Ok(global), Ok(local)) => {
let mut keys = keymap::default();
Expand All @@ -84,6 +88,18 @@ impl Config {
.map_err(ConfigLoadError::BadConfig)?,
};

let icons: Icons = match (global.icons, local.icons) {
(None, None) => Icons::default(),
(None, Some(val)) | (Some(val), None) => {
val.try_into().map_err(ConfigLoadError::BadConfig)?
}
(Some(global), Some(local)) => merge_toml_values(global, local, 3)
.try_into()
.map_err(ConfigLoadError::BadConfig)?,
};

ICONS.store(Arc::new(icons));

Config {
theme: local.theme.or(global.theme),
keys,
Expand All @@ -100,6 +116,14 @@ impl Config {
if let Some(keymap) = config.keys {
merge_keys(&mut keys, keymap);
}

let icons = config.icons.map_or_else(
|| Ok(Icons::default()),
|val| val.try_into().map_err(ConfigLoadError::BadConfig),
)?;

ICONS.store(Arc::new(icons));

Config {
theme: config.theme,
keys,
Expand Down
5 changes: 4 additions & 1 deletion helix-term/src/handlers/document_colors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use helix_view::{
document::DocumentColorSwatches,
events::{DocumentDidChange, DocumentDidOpen, LanguageServerExited, LanguageServerInitialized},
handlers::{lsp::DocumentColorsEvent, Handlers},
icons::ICONS,
DocumentId, Editor, Theme,
};
use tokio::time::Instant;
Expand Down Expand Up @@ -120,9 +121,11 @@ fn attach_document_colors(
let mut color_swatches_padding = Vec::with_capacity(doc_colors.len());
let mut colors = Vec::with_capacity(doc_colors.len());

let icons = ICONS.load();

for (pos, color) in doc_colors {
color_swatches_padding.push(InlineAnnotation::new(pos, " "));
color_swatches.push(InlineAnnotation::new(pos, "■"));
color_swatches.push(InlineAnnotation::new(pos, icons.kind().color().glyph()));
colors.push(Theme::rgb_highlight(
(color.red * 255.) as u8,
(color.green * 255.) as u8,
Expand Down
28 changes: 25 additions & 3 deletions helix-term/src/ui/completion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use crate::{
use helix_core::snippets::{ActiveSnippet, RenderedSnippet, Snippet};
use helix_core::{self as core, chars, fuzzy::MATCHER, Change, Transaction};
use helix_lsp::{lsp, util, OffsetEncoding};
use helix_view::icons::ICONS;
use helix_view::{
editor::CompleteAction,
handlers::lsp::SignatureHelpInvoked,
Expand Down Expand Up @@ -45,7 +46,7 @@ impl menu::Item for CompletionItem {
CompletionItem::Other(core::CompletionItem { label, .. }) => label,
};

let kind = match self {
let mut kind = match self {
CompletionItem::Lsp(LspCompletionItem { item, .. }) => match item.kind {
Some(lsp::CompletionItemKind::TEXT) => "text".into(),
Some(lsp::CompletionItemKind::METHOD) => "method".into(),
Expand Down Expand Up @@ -78,9 +79,13 @@ impl menu::Item for CompletionItem {
})
.and_then(Color::from_hex)
.map_or("color".into(), |color| {
let icons = ICONS.load();
Spans::from(vec![
Span::raw("color "),
Span::styled("■", Style::default().fg(color)),
Span::styled(
icons.kind().color().glyph().to_string(),
Style::default().fg(color),
),
])
}),
Some(lsp::CompletionItemKind::FILE) => "file".into(),
Expand All @@ -101,11 +106,28 @@ impl menu::Item for CompletionItem {
CompletionItem::Other(core::CompletionItem { kind, .. }) => kind.as_ref().into(),
};

let icons = ICONS.load();
let name = &kind.0[0].content;

let is_folder = kind.0[0].content == "folder";

if let Some(icon) = icons.kind().get(name) {
kind.0[0].content = format!("{} {name}", icon.glyph()).into();

if let Some(color) = icon.color() {
kind.0[0].style = Style::default().fg(color);
} else if is_folder {
kind.0[0].style = *dir_style;
}
} else {
kind.0[0].content = format!("{name}").into();
}

let label = Span::styled(
label,
if deprecated {
Style::default().add_modifier(Modifier::CROSSED_OUT)
} else if kind.0[0].content == "folder" {
} else if is_folder {
*dir_style
} else {
Style::default()
Expand Down
15 changes: 14 additions & 1 deletion helix-term/src/ui/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ use helix_view::{
document::{Mode, SCRATCH_BUFFER_NAME},
editor::{CompleteAction, CursorShapeConfig},
graphics::{Color, CursorKind, Modifier, Rect, Style},
icons::ICONS,
input::{KeyEvent, MouseButton, MouseEvent, MouseEventKind},
keyboard::{KeyCode, KeyModifiers},
Document, Editor, Theme, View,
Expand Down Expand Up @@ -597,7 +598,19 @@ impl EditorView {
bufferline_inactive
};

let text = format!(" {}{} ", fname, if doc.is_modified() { "[+]" } else { "" });
let icons = ICONS.load();

let text = if let Some(icon) = icons.mime().get(doc.path(), doc.language_name()) {
format!(
" {} {} {}",
icon.glyph(),
fname,
if doc.is_modified() { "[+] " } else { "" }
)
} else {
format!(" {} {}", fname, if doc.is_modified() { "[+] " } else { "" })
};

let used_width = viewport.x.saturating_sub(x);
let rem_width = surface.area.width.saturating_sub(used_width);

Expand Down
Loading
Loading