Skip to content

Commit 2075616

Browse files
committed
feat: add support for basic icons
1 parent 8b952bb commit 2075616

File tree

14 files changed

+1344
-57
lines changed

14 files changed

+1344
-57
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

helix-term/src/commands.rs

Lines changed: 72 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ use helix_core::{
4444
use helix_view::{
4545
document::{FormatterError, Mode, SCRATCH_BUFFER_NAME},
4646
editor::Action,
47+
icons::ICONS,
4748
info::Info,
4849
input::KeyEvent,
4950
keyboard::KeyCode,
@@ -3178,11 +3179,32 @@ fn buffer_picker(cx: &mut Context) {
31783179
.path
31793180
.as_deref()
31803181
.map(helix_stdx::path::get_relative_path);
3181-
path.as_deref()
3182+
3183+
let name = path
3184+
.as_deref()
31823185
.and_then(Path::to_str)
3183-
.unwrap_or(SCRATCH_BUFFER_NAME)
3184-
.to_string()
3185-
.into()
3186+
.unwrap_or(SCRATCH_BUFFER_NAME);
3187+
let icons = ICONS.load();
3188+
3189+
let mut spans = Vec::with_capacity(2);
3190+
3191+
if let Some(icon) = icons
3192+
.mime()
3193+
.get(path.as_ref().map(|path| path.to_path_buf()).as_ref(), None)
3194+
{
3195+
if let Some(color) = icon.color() {
3196+
spans.push(Span::styled(
3197+
format!("{} ", icon.glyph()),
3198+
Style::default().fg(color),
3199+
));
3200+
} else {
3201+
spans.push(Span::raw(format!("{} ", icon.glyph())));
3202+
}
3203+
}
3204+
3205+
spans.push(Span::raw(name.to_string()));
3206+
3207+
Spans::from(spans).into()
31863208
}),
31873209
];
31883210
let picker = Picker::new(columns, 2, items, (), |cx, meta, action| {
@@ -3241,11 +3263,32 @@ fn jumplist_picker(cx: &mut Context) {
32413263
.path
32423264
.as_deref()
32433265
.map(helix_stdx::path::get_relative_path);
3244-
path.as_deref()
3266+
3267+
let name = path
3268+
.as_deref()
32453269
.and_then(Path::to_str)
3246-
.unwrap_or(SCRATCH_BUFFER_NAME)
3247-
.to_string()
3248-
.into()
3270+
.unwrap_or(SCRATCH_BUFFER_NAME);
3271+
let icons = ICONS.load();
3272+
3273+
let mut spans = Vec::with_capacity(2);
3274+
3275+
if let Some(icon) = icons
3276+
.mime()
3277+
.get(path.as_ref().map(|path| path.to_path_buf()).as_ref(), None)
3278+
{
3279+
if let Some(color) = icon.color() {
3280+
spans.push(Span::styled(
3281+
format!("{} ", icon.glyph()),
3282+
Style::default().fg(color),
3283+
));
3284+
} else {
3285+
spans.push(Span::raw(format!("{} ", icon.glyph())));
3286+
}
3287+
}
3288+
3289+
spans.push(Span::raw(name.to_string()));
3290+
3291+
Spans::from(spans).into()
32493292
}),
32503293
ui::PickerColumn::new("flags", |item: &JumpMeta, _| {
32513294
let mut flags = Vec::new();
@@ -3315,12 +3358,28 @@ fn changed_file_picker(cx: &mut Context) {
33153358

33163359
let columns = [
33173360
PickerColumn::new("change", |change: &FileChange, data: &FileChangeData| {
3361+
let icons = ICONS.load();
33183362
match change {
3319-
FileChange::Untracked { .. } => Span::styled("+ untracked", data.style_untracked),
3320-
FileChange::Modified { .. } => Span::styled("~ modified", data.style_modified),
3321-
FileChange::Conflict { .. } => Span::styled("x conflict", data.style_conflict),
3322-
FileChange::Deleted { .. } => Span::styled("- deleted", data.style_deleted),
3323-
FileChange::Renamed { .. } => Span::styled("> renamed", data.style_renamed),
3363+
FileChange::Untracked { .. } => Span::styled(
3364+
format!("{} untracked", icons.vcs().added()),
3365+
data.style_untracked,
3366+
),
3367+
FileChange::Modified { .. } => Span::styled(
3368+
format!("{} modified", icons.vcs().modified()),
3369+
data.style_modified,
3370+
),
3371+
FileChange::Conflict { .. } => Span::styled(
3372+
format!("{} conflict", icons.vcs().conflict()),
3373+
data.style_conflict,
3374+
),
3375+
FileChange::Deleted { .. } => Span::styled(
3376+
format!("{} deleted", icons.vcs().removed()),
3377+
data.style_deleted,
3378+
),
3379+
FileChange::Renamed { .. } => Span::styled(
3380+
format!("{} renamed", icons.vcs().renamed()),
3381+
data.style_renamed,
3382+
),
33243383
}
33253384
.into()
33263385
}),

helix-term/src/commands/lsp.rs

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ use helix_view::{
2222
document::{DocumentInlayHints, DocumentInlayHintsId},
2323
editor::Action,
2424
handlers::lsp::SignatureHelpInvoked,
25+
icons::ICONS,
2526
theme::Style,
2627
Document, View,
2728
};
@@ -182,7 +183,7 @@ fn display_symbol_kind(kind: lsp::SymbolKind) -> &'static str {
182183
lsp::SymbolKind::OBJECT => "object",
183184
lsp::SymbolKind::KEY => "key",
184185
lsp::SymbolKind::NULL => "null",
185-
lsp::SymbolKind::ENUM_MEMBER => "enummem",
186+
lsp::SymbolKind::ENUM_MEMBER => "enum_member",
186187
lsp::SymbolKind::STRUCT => "struct",
187188
lsp::SymbolKind::EVENT => "event",
188189
lsp::SymbolKind::OPERATOR => "operator",
@@ -242,11 +243,22 @@ fn diag_picker(
242243
ui::PickerColumn::new(
243244
"severity",
244245
|item: &PickerDiagnostic, styles: &DiagnosticStyles| {
246+
let icons = ICONS.load();
245247
match item.diag.severity {
246-
Some(DiagnosticSeverity::HINT) => Span::styled("HINT", styles.hint),
247-
Some(DiagnosticSeverity::INFORMATION) => Span::styled("INFO", styles.info),
248-
Some(DiagnosticSeverity::WARNING) => Span::styled("WARN", styles.warning),
249-
Some(DiagnosticSeverity::ERROR) => Span::styled("ERROR", styles.error),
248+
Some(DiagnosticSeverity::HINT) => {
249+
Span::styled(format!("{} HINT", icons.diagnostic().hint()), styles.hint)
250+
}
251+
Some(DiagnosticSeverity::INFORMATION) => {
252+
Span::styled(format!("{} INFO", icons.diagnostic().info()), styles.info)
253+
}
254+
Some(DiagnosticSeverity::WARNING) => Span::styled(
255+
format!("{} WARN", icons.diagnostic().warning()),
256+
styles.warning,
257+
),
258+
Some(DiagnosticSeverity::ERROR) => Span::styled(
259+
format!("{} ERROR", icons.diagnostic().error()),
260+
styles.error,
261+
),
250262
_ => Span::raw(""),
251263
}
252264
.into()
@@ -397,7 +409,22 @@ pub fn symbol_picker(cx: &mut Context) {
397409
let call = move |_editor: &mut Editor, compositor: &mut Compositor| {
398410
let columns = [
399411
ui::PickerColumn::new("kind", |item: &SymbolInformationItem, _| {
400-
display_symbol_kind(item.symbol.kind).into()
412+
let icons = ICONS.load();
413+
let name = display_symbol_kind(item.symbol.kind);
414+
415+
if let Some(icon) = icons.kind().get(name) {
416+
if let Some(color) = icon.color() {
417+
Span::styled(
418+
format!("{} {name}", icon.glyph()),
419+
Style::default().fg(color),
420+
)
421+
.into()
422+
} else {
423+
format!("{} {name}", icon.glyph()).into()
424+
}
425+
} else {
426+
name.into()
427+
}
401428
}),
402429
// Some symbols in the document symbol picker may have a URI that isn't
403430
// the current file. It should be rare though, so we concatenate that
@@ -515,7 +542,22 @@ pub fn workspace_symbol_picker(cx: &mut Context) {
515542
};
516543
let columns = [
517544
ui::PickerColumn::new("kind", |item: &SymbolInformationItem, _| {
518-
display_symbol_kind(item.symbol.kind).into()
545+
let icons = ICONS.load();
546+
let name = display_symbol_kind(item.symbol.kind);
547+
548+
if let Some(icon) = icons.kind().get(name) {
549+
if let Some(color) = icon.color() {
550+
Span::styled(
551+
format!("{} {name}", icon.glyph()),
552+
Style::default().fg(color),
553+
)
554+
.into()
555+
} else {
556+
format!("{} {name}", icon.glyph()).into()
557+
}
558+
} else {
559+
name.into()
560+
}
519561
}),
520562
ui::PickerColumn::new("name", |item: &SymbolInformationItem, _| {
521563
item.symbol.name.as_str().into()

helix-term/src/config.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ use crate::keymap;
22
use crate::keymap::{merge_keys, KeyTrie};
33
use helix_loader::merge_toml_values;
44
use helix_view::document::Mode;
5+
use helix_view::icons::{Icons, ICONS};
56
use serde::Deserialize;
67
use std::collections::HashMap;
78
use std::fmt::Display;
89
use std::fs;
910
use std::io::Error as IOError;
11+
use std::sync::Arc;
1012
use toml::de::Error as TomlError;
1113

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

2730
impl Default for Config {
@@ -64,6 +67,7 @@ impl Config {
6467
global.and_then(|file| toml::from_str(&file).map_err(ConfigLoadError::BadConfig));
6568
let local_config: Result<ConfigRaw, ConfigLoadError> =
6669
local.and_then(|file| toml::from_str(&file).map_err(ConfigLoadError::BadConfig));
70+
6771
let res = match (global_config, local_config) {
6872
(Ok(global), Ok(local)) => {
6973
let mut keys = keymap::default();
@@ -84,6 +88,18 @@ impl Config {
8488
.map_err(ConfigLoadError::BadConfig)?,
8589
};
8690

91+
let icons: Icons = match (global.icons, local.icons) {
92+
(None, None) => Icons::default(),
93+
(None, Some(val)) | (Some(val), None) => {
94+
val.try_into().map_err(ConfigLoadError::BadConfig)?
95+
}
96+
(Some(global), Some(local)) => merge_toml_values(global, local, 3)
97+
.try_into()
98+
.map_err(ConfigLoadError::BadConfig)?,
99+
};
100+
101+
ICONS.store(Arc::new(icons));
102+
87103
Config {
88104
theme: local.theme.or(global.theme),
89105
keys,
@@ -100,6 +116,14 @@ impl Config {
100116
if let Some(keymap) = config.keys {
101117
merge_keys(&mut keys, keymap);
102118
}
119+
120+
let icons = config.icons.map_or_else(
121+
|| Ok(Icons::default()),
122+
|val| val.try_into().map_err(ConfigLoadError::BadConfig),
123+
)?;
124+
125+
ICONS.store(Arc::new(icons));
126+
103127
Config {
104128
theme: config.theme,
105129
keys,

helix-term/src/handlers/document_colors.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use helix_view::{
88
document::DocumentColorSwatches,
99
events::{DocumentDidChange, DocumentDidOpen, LanguageServerExited, LanguageServerInitialized},
1010
handlers::{lsp::DocumentColorsEvent, Handlers},
11+
icons::ICONS,
1112
DocumentId, Editor, Theme,
1213
};
1314
use tokio::time::Instant;
@@ -120,9 +121,11 @@ fn attach_document_colors(
120121
let mut color_swatches_padding = Vec::with_capacity(doc_colors.len());
121122
let mut colors = Vec::with_capacity(doc_colors.len());
122123

124+
let icons = ICONS.load();
125+
123126
for (pos, color) in doc_colors {
124127
color_swatches_padding.push(InlineAnnotation::new(pos, " "));
125-
color_swatches.push(InlineAnnotation::new(pos, "■"));
128+
color_swatches.push(InlineAnnotation::new(pos, icons.kind().color().glyph()));
126129
colors.push(Theme::rgb_highlight(
127130
(color.red * 255.) as u8,
128131
(color.green * 255.) as u8,

helix-term/src/ui/completion.rs

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use crate::{
99
use helix_core::snippets::{ActiveSnippet, RenderedSnippet, Snippet};
1010
use helix_core::{self as core, chars, fuzzy::MATCHER, Change, Transaction};
1111
use helix_lsp::{lsp, util, OffsetEncoding};
12+
use helix_view::icons::ICONS;
1213
use helix_view::{
1314
editor::CompleteAction,
1415
handlers::lsp::SignatureHelpInvoked,
@@ -45,7 +46,7 @@ impl menu::Item for CompletionItem {
4546
CompletionItem::Other(core::CompletionItem { label, .. }) => label,
4647
};
4748

48-
let kind = match self {
49+
let mut kind = match self {
4950
CompletionItem::Lsp(LspCompletionItem { item, .. }) => match item.kind {
5051
Some(lsp::CompletionItemKind::TEXT) => "text".into(),
5152
Some(lsp::CompletionItemKind::METHOD) => "method".into(),
@@ -78,9 +79,13 @@ impl menu::Item for CompletionItem {
7879
})
7980
.and_then(Color::from_hex)
8081
.map_or("color".into(), |color| {
82+
let icons = ICONS.load();
8183
Spans::from(vec![
8284
Span::raw("color "),
83-
Span::styled("■", Style::default().fg(color)),
85+
Span::styled(
86+
icons.kind().color().glyph().to_string(),
87+
Style::default().fg(color),
88+
),
8489
])
8590
}),
8691
Some(lsp::CompletionItemKind::FILE) => "file".into(),
@@ -101,11 +106,28 @@ impl menu::Item for CompletionItem {
101106
CompletionItem::Other(core::CompletionItem { kind, .. }) => kind.as_ref().into(),
102107
};
103108

109+
let icons = ICONS.load();
110+
let name = &kind.0[0].content;
111+
112+
let is_folder = kind.0[0].content == "folder";
113+
114+
if let Some(icon) = icons.kind().get(name) {
115+
kind.0[0].content = format!("{} {name}", icon.glyph()).into();
116+
117+
if let Some(color) = icon.color() {
118+
kind.0[0].style = Style::default().fg(color);
119+
} else if is_folder {
120+
kind.0[0].style = *dir_style;
121+
}
122+
} else {
123+
kind.0[0].content = format!("{name}").into();
124+
}
125+
104126
let label = Span::styled(
105127
label,
106128
if deprecated {
107129
Style::default().add_modifier(Modifier::CROSSED_OUT)
108-
} else if kind.0[0].content == "folder" {
130+
} else if is_folder {
109131
*dir_style
110132
} else {
111133
Style::default()

helix-term/src/ui/editor.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ use helix_view::{
2727
document::{Mode, SCRATCH_BUFFER_NAME},
2828
editor::{CompleteAction, CursorShapeConfig},
2929
graphics::{Color, CursorKind, Modifier, Rect, Style},
30+
icons::ICONS,
3031
input::{KeyEvent, MouseButton, MouseEvent, MouseEventKind},
3132
keyboard::{KeyCode, KeyModifiers},
3233
Document, Editor, Theme, View,
@@ -647,7 +648,19 @@ impl EditorView {
647648
bufferline_inactive
648649
};
649650

650-
let text = format!(" {}{} ", fname, if doc.is_modified() { "[+]" } else { "" });
651+
let icons = ICONS.load();
652+
653+
let text = if let Some(icon) = icons.mime().get(doc.path(), doc.language_name()) {
654+
format!(
655+
" {} {} {}",
656+
icon.glyph(),
657+
fname,
658+
if doc.is_modified() { "[+] " } else { "" }
659+
)
660+
} else {
661+
format!(" {} {}", fname, if doc.is_modified() { "[+] " } else { "" })
662+
};
663+
651664
let used_width = viewport.x.saturating_sub(x);
652665
let rem_width = surface.area.width.saturating_sub(used_width);
653666

0 commit comments

Comments
 (0)