Skip to content

Commit 5d96ed2

Browse files
committed
feat: add support for basic icons
1 parent d24e4fc commit 5d96ed2

File tree

14 files changed

+1294
-49
lines changed

14 files changed

+1294
-49
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: 22 additions & 5 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,
@@ -3315,12 +3316,28 @@ fn changed_file_picker(cx: &mut Context) {
33153316

33163317
let columns = [
33173318
PickerColumn::new("change", |change: &FileChange, data: &FileChangeData| {
3319+
let icons = ICONS.load();
33183320
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),
3321+
FileChange::Untracked { .. } => Span::styled(
3322+
format!("{} untracked", icons.vcs().added()),
3323+
data.style_untracked,
3324+
),
3325+
FileChange::Modified { .. } => Span::styled(
3326+
format!("{} modified", icons.vcs().modified()),
3327+
data.style_modified,
3328+
),
3329+
FileChange::Conflict { .. } => Span::styled(
3330+
format!("{} conflict", icons.vcs().conflict()),
3331+
data.style_conflict,
3332+
),
3333+
FileChange::Deleted { .. } => Span::styled(
3334+
format!("{} deleted", icons.vcs().removed()),
3335+
data.style_deleted,
3336+
),
3337+
FileChange::Renamed { .. } => Span::styled(
3338+
format!("{} renamed", icons.vcs().renamed()),
3339+
data.style_renamed,
3340+
),
33243341
}
33253342
.into()
33263343
}),

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

helix-term/src/ui/mod.rs

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ use crate::job::{self, Callback};
2020
pub use completion::Completion;
2121
pub use editor::EditorView;
2222
use helix_stdx::rope;
23+
use helix_view::icons::ICONS;
2324
use helix_view::theme::Style;
2425
pub use markdown::Markdown;
2526
pub use menu::Menu;
@@ -249,7 +250,21 @@ pub fn file_picker(editor: &Editor, root: PathBuf) -> FilePicker {
249250
"path",
250251
|item: &PathBuf, data: &FilePickerData| {
251252
let path = item.strip_prefix(&data.root).unwrap_or(item);
252-
let mut spans = Vec::with_capacity(3);
253+
let mut spans = Vec::with_capacity(4);
254+
255+
let icons = ICONS.load();
256+
257+
if let Some(icon) = icons.mime().get(Some(&path.to_path_buf()), None) {
258+
if let Some(color) = icon.color() {
259+
spans.push(Span::styled(
260+
format!("{} ", icon.glyph()),
261+
Style::default().fg(color),
262+
));
263+
} else {
264+
spans.push(Span::raw(format!("{} ", icon.glyph())));
265+
}
266+
}
267+
253268
if let Some(dirs) = path.parent().filter(|p| !p.as_os_str().is_empty()) {
254269
spans.extend([
255270
Span::styled(dirs.to_string_lossy(), data.directory_style),
@@ -310,8 +325,27 @@ pub fn file_explorer(root: PathBuf, editor: &Editor) -> Result<FileExplorer, std
310325
"path",
311326
|(path, is_dir): &(PathBuf, bool), (root, directory_style): &(PathBuf, Style)| {
312327
let name = path.strip_prefix(root).unwrap_or(path).to_string_lossy();
328+
let icons = ICONS.load();
313329
if *is_dir {
314-
Span::styled(format!("{}/", name), *directory_style).into()
330+
if let Some(icon) = icons.mime().directory() {
331+
Span::styled(format!("{icon} {name}/"), *directory_style).into()
332+
} else {
333+
Span::styled(format!("{name}/"), *directory_style).into()
334+
}
335+
} else if let Some(icon) = icons.mime().get(Some(path), None).cloned() {
336+
let mut spans = Vec::with_capacity(2);
337+
if let Some(color) = icon.color() {
338+
let icon =
339+
Span::styled(format!("{} ", icon.glyph()), Style::default().fg(color));
340+
let filename = Span::raw(name);
341+
342+
spans.push(icon);
343+
spans.push(filename);
344+
} else {
345+
spans.push(Span::raw(format!("{} ", icon.glyph())));
346+
spans.push(Span::raw(name));
347+
}
348+
Spans::from(spans).into()
315349
} else {
316350
name.into()
317351
}

0 commit comments

Comments
 (0)