mirror of https://github.com/helix-editor/helix
Compare commits
2 Commits
27d8bd20bf
...
5ce27b0087
Author | SHA1 | Date |
---|---|---|
|
5ce27b0087 | |
|
70deab1b19 |
|
@ -1644,6 +1644,7 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"slotmap",
|
"slotmap",
|
||||||
|
"smartstring",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.12",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
@ -2577,6 +2578,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29"
|
checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
|
"serde",
|
||||||
"static_assertions",
|
"static_assertions",
|
||||||
"version_check",
|
"version_check",
|
||||||
]
|
]
|
||||||
|
|
|
@ -14,7 +14,7 @@ use helix_vcs::{FileChange, Hunk};
|
||||||
pub use lsp::*;
|
pub use lsp::*;
|
||||||
pub use syntax::*;
|
pub use syntax::*;
|
||||||
use tui::{
|
use tui::{
|
||||||
text::{Span, Spans},
|
text::{Span, Spans, ToSpan},
|
||||||
widgets::Cell,
|
widgets::Cell,
|
||||||
};
|
};
|
||||||
pub use typed::*;
|
pub use typed::*;
|
||||||
|
@ -46,6 +46,7 @@ use helix_core::{
|
||||||
use helix_view::{
|
use helix_view::{
|
||||||
document::{FormatterError, Mode, SCRATCH_BUFFER_NAME},
|
document::{FormatterError, Mode, SCRATCH_BUFFER_NAME},
|
||||||
editor::Action,
|
editor::Action,
|
||||||
|
icons::ICONS,
|
||||||
info::Info,
|
info::Info,
|
||||||
input::KeyEvent,
|
input::KeyEvent,
|
||||||
keyboard::KeyCode,
|
keyboard::KeyCode,
|
||||||
|
@ -2496,12 +2497,22 @@ fn global_search(cx: &mut Context) {
|
||||||
.expect("global search paths are normalized (can't end in `..`)")
|
.expect("global search paths are normalized (can't end in `..`)")
|
||||||
.to_string_lossy();
|
.to_string_lossy();
|
||||||
|
|
||||||
Cell::from(Spans::from(vec![
|
let mut spans = Vec::with_capacity(5);
|
||||||
|
|
||||||
|
let icons = ICONS.load();
|
||||||
|
|
||||||
|
if let Some(icon) = icons.fs().from_path(&path) {
|
||||||
|
spans.push(icon.to_span_with(|icon| format!("{icon} ")));
|
||||||
|
}
|
||||||
|
|
||||||
|
spans.extend_from_slice(&[
|
||||||
Span::styled(directories, config.directory_style),
|
Span::styled(directories, config.directory_style),
|
||||||
Span::raw(filename),
|
Span::raw(filename),
|
||||||
Span::styled(":", config.colon_style),
|
Span::styled(":", config.colon_style),
|
||||||
Span::styled((item.line_num + 1).to_string(), config.number_style),
|
Span::styled((item.line_num + 1).to_string(), config.number_style),
|
||||||
]))
|
]);
|
||||||
|
|
||||||
|
Cell::from(Spans::from(spans))
|
||||||
}),
|
}),
|
||||||
PickerColumn::hidden("contents"),
|
PickerColumn::hidden("contents"),
|
||||||
];
|
];
|
||||||
|
@ -3190,11 +3201,23 @@ fn buffer_picker(cx: &mut Context) {
|
||||||
.path
|
.path
|
||||||
.as_deref()
|
.as_deref()
|
||||||
.map(helix_stdx::path::get_relative_path);
|
.map(helix_stdx::path::get_relative_path);
|
||||||
path.as_deref()
|
|
||||||
|
let name = path
|
||||||
|
.as_deref()
|
||||||
.and_then(Path::to_str)
|
.and_then(Path::to_str)
|
||||||
.unwrap_or(SCRATCH_BUFFER_NAME)
|
.unwrap_or(SCRATCH_BUFFER_NAME);
|
||||||
.to_string()
|
|
||||||
.into()
|
let icons = ICONS.load();
|
||||||
|
|
||||||
|
let mut spans = Vec::with_capacity(2);
|
||||||
|
|
||||||
|
if let Some(icon) = icons.fs().from_optional_path(path.as_deref()) {
|
||||||
|
spans.push(icon.to_span_with(|icon| format!("{icon} ")));
|
||||||
|
}
|
||||||
|
|
||||||
|
spans.push(name.to_string().into());
|
||||||
|
|
||||||
|
Spans::from(spans).into()
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
let picker = Picker::new(columns, 2, items, (), |cx, meta, action| {
|
let picker = Picker::new(columns, 2, items, (), |cx, meta, action| {
|
||||||
|
@ -3253,11 +3276,22 @@ fn jumplist_picker(cx: &mut Context) {
|
||||||
.path
|
.path
|
||||||
.as_deref()
|
.as_deref()
|
||||||
.map(helix_stdx::path::get_relative_path);
|
.map(helix_stdx::path::get_relative_path);
|
||||||
path.as_deref()
|
|
||||||
|
let name = path
|
||||||
|
.as_deref()
|
||||||
.and_then(Path::to_str)
|
.and_then(Path::to_str)
|
||||||
.unwrap_or(SCRATCH_BUFFER_NAME)
|
.unwrap_or(SCRATCH_BUFFER_NAME);
|
||||||
.to_string()
|
let icons = ICONS.load();
|
||||||
.into()
|
|
||||||
|
let mut spans = Vec::with_capacity(2);
|
||||||
|
|
||||||
|
if let Some(icon) = icons.fs().from_optional_path(path.as_deref()) {
|
||||||
|
spans.push(icon.to_span_with(|icon| format!("{icon} ")));
|
||||||
|
}
|
||||||
|
|
||||||
|
spans.push(name.to_string().into());
|
||||||
|
|
||||||
|
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();
|
||||||
|
@ -3327,12 +3361,43 @@ fn changed_file_picker(cx: &mut Context) {
|
||||||
|
|
||||||
let columns = [
|
let columns = [
|
||||||
PickerColumn::new("change", |change: &FileChange, data: &FileChangeData| {
|
PickerColumn::new("change", |change: &FileChange, data: &FileChangeData| {
|
||||||
|
let icons = ICONS.load();
|
||||||
match change {
|
match change {
|
||||||
FileChange::Untracked { .. } => Span::styled("+ untracked", data.style_untracked),
|
FileChange::Untracked { .. } => Span::styled(
|
||||||
FileChange::Modified { .. } => Span::styled("~ modified", data.style_modified),
|
match icons.vcs().added() {
|
||||||
FileChange::Conflict { .. } => Span::styled("x conflict", data.style_conflict),
|
Some(icon) => Cow::from(format!("{icon} untracked")),
|
||||||
FileChange::Deleted { .. } => Span::styled("- deleted", data.style_deleted),
|
None => Cow::from("untracked"),
|
||||||
FileChange::Renamed { .. } => Span::styled("> renamed", data.style_renamed),
|
},
|
||||||
|
data.style_untracked,
|
||||||
|
),
|
||||||
|
FileChange::Modified { .. } => Span::styled(
|
||||||
|
match icons.vcs().modified() {
|
||||||
|
Some(icon) => Cow::from(format!("{icon} modified")),
|
||||||
|
None => Cow::from("modified"),
|
||||||
|
},
|
||||||
|
data.style_modified,
|
||||||
|
),
|
||||||
|
FileChange::Conflict { .. } => Span::styled(
|
||||||
|
match icons.vcs().conflict() {
|
||||||
|
Some(icon) => Cow::from(format!("{icon} conflict")),
|
||||||
|
None => Cow::from("conflict"),
|
||||||
|
},
|
||||||
|
data.style_conflict,
|
||||||
|
),
|
||||||
|
FileChange::Deleted { .. } => Span::styled(
|
||||||
|
match icons.vcs().removed() {
|
||||||
|
Some(icon) => Cow::from(format!("{icon} deleted")),
|
||||||
|
None => Cow::from("deleted"),
|
||||||
|
},
|
||||||
|
data.style_deleted,
|
||||||
|
),
|
||||||
|
FileChange::Renamed { .. } => Span::styled(
|
||||||
|
match icons.vcs().renamed() {
|
||||||
|
Some(icon) => Cow::from(format!("{icon} renamed")),
|
||||||
|
None => Cow::from("renamed"),
|
||||||
|
},
|
||||||
|
data.style_renamed,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
.into()
|
.into()
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -9,7 +9,10 @@ use helix_lsp::{
|
||||||
Client, LanguageServerId, OffsetEncoding,
|
Client, LanguageServerId, OffsetEncoding,
|
||||||
};
|
};
|
||||||
use tokio_stream::StreamExt;
|
use tokio_stream::StreamExt;
|
||||||
use tui::{text::Span, widgets::Row};
|
use tui::{
|
||||||
|
text::{Span, ToSpan},
|
||||||
|
widgets::Row,
|
||||||
|
};
|
||||||
|
|
||||||
use super::{align_view, push_jump, Align, Context, Editor};
|
use super::{align_view, push_jump, Align, Context, Editor};
|
||||||
|
|
||||||
|
@ -22,6 +25,7 @@ use helix_view::{
|
||||||
document::{DocumentInlayHints, DocumentInlayHintsId},
|
document::{DocumentInlayHints, DocumentInlayHintsId},
|
||||||
editor::Action,
|
editor::Action,
|
||||||
handlers::lsp::SignatureHelpInvoked,
|
handlers::lsp::SignatureHelpInvoked,
|
||||||
|
icons::ICONS,
|
||||||
theme::Style,
|
theme::Style,
|
||||||
Document, View,
|
Document, View,
|
||||||
};
|
};
|
||||||
|
@ -182,7 +186,7 @@ fn display_symbol_kind(kind: lsp::SymbolKind) -> &'static str {
|
||||||
lsp::SymbolKind::OBJECT => "object",
|
lsp::SymbolKind::OBJECT => "object",
|
||||||
lsp::SymbolKind::KEY => "key",
|
lsp::SymbolKind::KEY => "key",
|
||||||
lsp::SymbolKind::NULL => "null",
|
lsp::SymbolKind::NULL => "null",
|
||||||
lsp::SymbolKind::ENUM_MEMBER => "enummem",
|
lsp::SymbolKind::ENUM_MEMBER => "enum_member",
|
||||||
lsp::SymbolKind::STRUCT => "struct",
|
lsp::SymbolKind::STRUCT => "struct",
|
||||||
lsp::SymbolKind::EVENT => "event",
|
lsp::SymbolKind::EVENT => "event",
|
||||||
lsp::SymbolKind::OPERATOR => "operator",
|
lsp::SymbolKind::OPERATOR => "operator",
|
||||||
|
@ -242,11 +246,22 @@ fn diag_picker(
|
||||||
ui::PickerColumn::new(
|
ui::PickerColumn::new(
|
||||||
"severity",
|
"severity",
|
||||||
|item: &PickerDiagnostic, styles: &DiagnosticStyles| {
|
|item: &PickerDiagnostic, styles: &DiagnosticStyles| {
|
||||||
|
let icons = ICONS.load();
|
||||||
match item.diag.severity {
|
match item.diag.severity {
|
||||||
Some(DiagnosticSeverity::HINT) => Span::styled("HINT", styles.hint),
|
Some(DiagnosticSeverity::HINT) => {
|
||||||
Some(DiagnosticSeverity::INFORMATION) => Span::styled("INFO", styles.info),
|
Span::styled(format!("{} HINT", icons.diagnostic().hint()), styles.hint)
|
||||||
Some(DiagnosticSeverity::WARNING) => Span::styled("WARN", styles.warning),
|
}
|
||||||
Some(DiagnosticSeverity::ERROR) => Span::styled("ERROR", styles.error),
|
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(""),
|
_ => Span::raw(""),
|
||||||
}
|
}
|
||||||
.into()
|
.into()
|
||||||
|
@ -400,7 +415,14 @@ 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, _| {
|
||||||
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) {
|
||||||
|
icon.to_span_with(|icon| format!("{icon} {name}")).into()
|
||||||
|
} else {
|
||||||
|
name.into()
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
// Some symbols in the document symbol picker may have a URI that isn't
|
// 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
|
// the current file. It should be rare though, so we concatenate that
|
||||||
|
@ -518,7 +540,14 @@ pub fn workspace_symbol_picker(cx: &mut Context) {
|
||||||
};
|
};
|
||||||
let columns = [
|
let columns = [
|
||||||
ui::PickerColumn::new("kind", |item: &SymbolInformationItem, _| {
|
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) {
|
||||||
|
icon.to_span_with(|icon| format!("{icon} {name}")).into()
|
||||||
|
} else {
|
||||||
|
name.into()
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
ui::PickerColumn::new("name", |item: &SymbolInformationItem, _| {
|
ui::PickerColumn::new("name", |item: &SymbolInformationItem, _| {
|
||||||
item.symbol.name.as_str().into()
|
item.symbol.name.as_str().into()
|
||||||
|
|
|
@ -2,11 +2,13 @@ use crate::keymap;
|
||||||
use crate::keymap::{merge_keys, KeyTrie};
|
use crate::keymap::{merge_keys, KeyTrie};
|
||||||
use helix_loader::merge_toml_values;
|
use helix_loader::merge_toml_values;
|
||||||
use helix_view::document::Mode;
|
use helix_view::document::Mode;
|
||||||
|
use helix_view::icons::{Icons, ICONS};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io::Error as IOError;
|
use std::io::Error as IOError;
|
||||||
|
use std::sync::Arc;
|
||||||
use toml::de::Error as TomlError;
|
use toml::de::Error as TomlError;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
@ -22,6 +24,7 @@ pub struct ConfigRaw {
|
||||||
pub theme: Option<String>,
|
pub theme: Option<String>,
|
||||||
pub keys: Option<HashMap<Mode, KeyTrie>>,
|
pub keys: Option<HashMap<Mode, KeyTrie>>,
|
||||||
pub editor: Option<toml::Value>,
|
pub editor: Option<toml::Value>,
|
||||||
|
pub icons: Option<toml::Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Config {
|
impl Default for Config {
|
||||||
|
@ -64,6 +67,7 @@ impl Config {
|
||||||
global.and_then(|file| toml::from_str(&file).map_err(ConfigLoadError::BadConfig));
|
global.and_then(|file| toml::from_str(&file).map_err(ConfigLoadError::BadConfig));
|
||||||
let local_config: Result<ConfigRaw, ConfigLoadError> =
|
let local_config: Result<ConfigRaw, ConfigLoadError> =
|
||||||
local.and_then(|file| toml::from_str(&file).map_err(ConfigLoadError::BadConfig));
|
local.and_then(|file| toml::from_str(&file).map_err(ConfigLoadError::BadConfig));
|
||||||
|
|
||||||
let res = match (global_config, local_config) {
|
let res = match (global_config, local_config) {
|
||||||
(Ok(global), Ok(local)) => {
|
(Ok(global), Ok(local)) => {
|
||||||
let mut keys = keymap::default();
|
let mut keys = keymap::default();
|
||||||
|
@ -84,6 +88,18 @@ impl Config {
|
||||||
.map_err(ConfigLoadError::BadConfig)?,
|
.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 {
|
Config {
|
||||||
theme: local.theme.or(global.theme),
|
theme: local.theme.or(global.theme),
|
||||||
keys,
|
keys,
|
||||||
|
@ -100,6 +116,14 @@ impl Config {
|
||||||
if let Some(keymap) = config.keys {
|
if let Some(keymap) = config.keys {
|
||||||
merge_keys(&mut keys, keymap);
|
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 {
|
Config {
|
||||||
theme: config.theme,
|
theme: config.theme,
|
||||||
keys,
|
keys,
|
||||||
|
|
|
@ -8,6 +8,7 @@ use helix_view::{
|
||||||
document::DocumentColorSwatches,
|
document::DocumentColorSwatches,
|
||||||
events::{DocumentDidChange, DocumentDidOpen, LanguageServerExited, LanguageServerInitialized},
|
events::{DocumentDidChange, DocumentDidOpen, LanguageServerExited, LanguageServerInitialized},
|
||||||
handlers::{lsp::DocumentColorsEvent, Handlers},
|
handlers::{lsp::DocumentColorsEvent, Handlers},
|
||||||
|
icons::ICONS,
|
||||||
DocumentId, Editor, Theme,
|
DocumentId, Editor, Theme,
|
||||||
};
|
};
|
||||||
use tokio::time::Instant;
|
use tokio::time::Instant;
|
||||||
|
@ -124,9 +125,11 @@ fn attach_document_colors(
|
||||||
let mut color_swatches_padding = Vec::with_capacity(doc_colors.len());
|
let mut color_swatches_padding = Vec::with_capacity(doc_colors.len());
|
||||||
let mut colors = 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 {
|
for (pos, color) in doc_colors {
|
||||||
color_swatches_padding.push(InlineAnnotation::new(pos, " "));
|
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(
|
colors.push(Theme::rgb_highlight(
|
||||||
(color.red * 255.) as u8,
|
(color.red * 255.) as u8,
|
||||||
(color.green * 255.) as u8,
|
(color.green * 255.) as u8,
|
||||||
|
|
|
@ -9,6 +9,7 @@ use crate::{
|
||||||
use helix_core::snippets::{ActiveSnippet, RenderedSnippet, Snippet};
|
use helix_core::snippets::{ActiveSnippet, RenderedSnippet, Snippet};
|
||||||
use helix_core::{self as core, chars, fuzzy::MATCHER, Change, Transaction};
|
use helix_core::{self as core, chars, fuzzy::MATCHER, Change, Transaction};
|
||||||
use helix_lsp::{lsp, util, OffsetEncoding};
|
use helix_lsp::{lsp, util, OffsetEncoding};
|
||||||
|
use helix_view::icons::ICONS;
|
||||||
use helix_view::{
|
use helix_view::{
|
||||||
editor::CompleteAction,
|
editor::CompleteAction,
|
||||||
handlers::lsp::SignatureHelpInvoked,
|
handlers::lsp::SignatureHelpInvoked,
|
||||||
|
@ -45,7 +46,7 @@ impl menu::Item for CompletionItem {
|
||||||
CompletionItem::Other(core::CompletionItem { label, .. }) => label,
|
CompletionItem::Other(core::CompletionItem { label, .. }) => label,
|
||||||
};
|
};
|
||||||
|
|
||||||
let kind = match self {
|
let mut kind = match self {
|
||||||
CompletionItem::Lsp(LspCompletionItem { item, .. }) => match item.kind {
|
CompletionItem::Lsp(LspCompletionItem { item, .. }) => match item.kind {
|
||||||
Some(lsp::CompletionItemKind::TEXT) => "text".into(),
|
Some(lsp::CompletionItemKind::TEXT) => "text".into(),
|
||||||
Some(lsp::CompletionItemKind::METHOD) => "method".into(),
|
Some(lsp::CompletionItemKind::METHOD) => "method".into(),
|
||||||
|
@ -78,9 +79,13 @@ impl menu::Item for CompletionItem {
|
||||||
})
|
})
|
||||||
.and_then(Color::from_hex)
|
.and_then(Color::from_hex)
|
||||||
.map_or("color".into(), |color| {
|
.map_or("color".into(), |color| {
|
||||||
|
let icons = ICONS.load();
|
||||||
Spans::from(vec![
|
Spans::from(vec![
|
||||||
Span::raw("color "),
|
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(),
|
Some(lsp::CompletionItemKind::FILE) => "file".into(),
|
||||||
|
@ -101,11 +106,28 @@ impl menu::Item for CompletionItem {
|
||||||
CompletionItem::Other(core::CompletionItem { kind, .. }) => kind.as_ref().into(),
|
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!("{icon} {name}").into();
|
||||||
|
|
||||||
|
if let Some(style) = icon.color().map(|color| Style::default().fg(color)) {
|
||||||
|
kind.0[0].style = style;
|
||||||
|
} else if is_folder {
|
||||||
|
kind.0[0].style = *dir_style;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
kind.0[0].content = format!("{name}").into();
|
||||||
|
}
|
||||||
|
|
||||||
let label = Span::styled(
|
let label = Span::styled(
|
||||||
label,
|
label,
|
||||||
if deprecated {
|
if deprecated {
|
||||||
Style::default().add_modifier(Modifier::CROSSED_OUT)
|
Style::default().add_modifier(Modifier::CROSSED_OUT)
|
||||||
} else if kind.0[0].content == "folder" {
|
} else if is_folder {
|
||||||
*dir_style
|
*dir_style
|
||||||
} else {
|
} else {
|
||||||
Style::default()
|
Style::default()
|
||||||
|
|
|
@ -24,9 +24,10 @@ use helix_core::{
|
||||||
};
|
};
|
||||||
use helix_view::{
|
use helix_view::{
|
||||||
annotations::diagnostics::DiagnosticFilter,
|
annotations::diagnostics::DiagnosticFilter,
|
||||||
document::{Mode, SCRATCH_BUFFER_NAME},
|
document::{Mode, DEFAULT_LANGUAGE_NAME, SCRATCH_BUFFER_NAME},
|
||||||
editor::{CompleteAction, CursorShapeConfig},
|
editor::{CompleteAction, CursorShapeConfig},
|
||||||
graphics::{Color, CursorKind, Modifier, Rect, Style},
|
graphics::{Color, CursorKind, Modifier, Rect, Style},
|
||||||
|
icons::ICONS,
|
||||||
input::{KeyEvent, MouseButton, MouseEvent, MouseEventKind},
|
input::{KeyEvent, MouseButton, MouseEvent, MouseEventKind},
|
||||||
keyboard::{KeyCode, KeyModifiers},
|
keyboard::{KeyCode, KeyModifiers},
|
||||||
Document, Editor, Theme, View,
|
Document, Editor, Theme, View,
|
||||||
|
@ -582,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)
|
||||||
|
@ -597,7 +598,45 @@ impl EditorView {
|
||||||
bufferline_inactive
|
bufferline_inactive
|
||||||
};
|
};
|
||||||
|
|
||||||
let text = format!(" {}{} ", fname, if doc.is_modified() { "[+]" } else { "" });
|
let lang = doc.language_name().unwrap_or(DEFAULT_LANGUAGE_NAME);
|
||||||
|
|
||||||
|
let icons = ICONS.load();
|
||||||
|
|
||||||
|
// 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
|
||||||
|
.fs()
|
||||||
|
.from_optional_path_or_lang(doc.path().map(|path| path.as_path()), lang)
|
||||||
|
{
|
||||||
|
let used_width = viewport.x.saturating_sub(x);
|
||||||
|
let rem_width = surface.area.width.saturating_sub(used_width);
|
||||||
|
|
||||||
|
let style = icon.color().map_or(style, |color| style.fg(color));
|
||||||
|
|
||||||
|
x = surface
|
||||||
|
.set_stringn(x, viewport.y, format!(" {icon}"), rem_width as usize, style)
|
||||||
|
.0;
|
||||||
|
|
||||||
|
if x >= surface.area.right() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let text = format!(" {} {}", fname, if doc.is_modified() { "[+] " } else { "" });
|
||||||
|
|
||||||
let used_width = viewport.x.saturating_sub(x);
|
let used_width = viewport.x.saturating_sub(x);
|
||||||
let rem_width = surface.area.width.saturating_sub(used_width);
|
let rem_width = surface.area.width.saturating_sub(used_width);
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ use crate::job::{self, Callback};
|
||||||
pub use completion::Completion;
|
pub use completion::Completion;
|
||||||
pub use editor::EditorView;
|
pub use editor::EditorView;
|
||||||
use helix_stdx::rope;
|
use helix_stdx::rope;
|
||||||
|
use helix_view::icons::ICONS;
|
||||||
use helix_view::theme::Style;
|
use helix_view::theme::Style;
|
||||||
pub use markdown::Markdown;
|
pub use markdown::Markdown;
|
||||||
pub use menu::Menu;
|
pub use menu::Menu;
|
||||||
|
@ -30,7 +31,7 @@ pub use spinner::{ProgressSpinners, Spinner};
|
||||||
pub use text::Text;
|
pub use text::Text;
|
||||||
|
|
||||||
use helix_view::Editor;
|
use helix_view::Editor;
|
||||||
use tui::text::{Span, Spans};
|
use tui::text::{Span, Spans, ToSpan};
|
||||||
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::{error::Error, path::PathBuf};
|
use std::{error::Error, path::PathBuf};
|
||||||
|
@ -249,7 +250,14 @@ pub fn file_picker(editor: &Editor, root: PathBuf) -> FilePicker {
|
||||||
"path",
|
"path",
|
||||||
|item: &PathBuf, data: &FilePickerData| {
|
|item: &PathBuf, data: &FilePickerData| {
|
||||||
let path = item.strip_prefix(&data.root).unwrap_or(item);
|
let path = item.strip_prefix(&data.root).unwrap_or(item);
|
||||||
let mut spans = Vec::with_capacity(3);
|
let mut spans = Vec::with_capacity(4);
|
||||||
|
|
||||||
|
let icons = ICONS.load();
|
||||||
|
|
||||||
|
if let Some(icon) = icons.fs().from_path(path) {
|
||||||
|
spans.push(icon.to_span_with(|icon| format!("{icon} ")));
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(dirs) = path.parent().filter(|p| !p.as_os_str().is_empty()) {
|
if let Some(dirs) = path.parent().filter(|p| !p.as_os_str().is_empty()) {
|
||||||
spans.extend([
|
spans.extend([
|
||||||
Span::styled(dirs.to_string_lossy(), data.directory_style),
|
Span::styled(dirs.to_string_lossy(), data.directory_style),
|
||||||
|
@ -310,8 +318,22 @@ pub fn file_explorer(root: PathBuf, editor: &Editor) -> Result<FileExplorer, std
|
||||||
"path",
|
"path",
|
||||||
|(path, is_dir): &(PathBuf, bool), (root, directory_style): &(PathBuf, Style)| {
|
|(path, is_dir): &(PathBuf, bool), (root, directory_style): &(PathBuf, Style)| {
|
||||||
let name = path.strip_prefix(root).unwrap_or(path).to_string_lossy();
|
let name = path.strip_prefix(root).unwrap_or(path).to_string_lossy();
|
||||||
|
|
||||||
|
let icons = ICONS.load();
|
||||||
|
|
||||||
if *is_dir {
|
if *is_dir {
|
||||||
Span::styled(format!("{}/", name), *directory_style).into()
|
if let Some(icon) = icons.fs().directory(false) {
|
||||||
|
Span::styled(format!("{icon} {name}/"), *directory_style).into()
|
||||||
|
} else {
|
||||||
|
Span::styled(format!("{name}/"), *directory_style).into()
|
||||||
|
}
|
||||||
|
} else if let Some(icon) = icons.fs().from_path(path) {
|
||||||
|
let mut spans = Vec::with_capacity(2);
|
||||||
|
|
||||||
|
spans.push(icon.to_span_with(|icon| format!("{icon} ")));
|
||||||
|
spans.push(Span::raw(name));
|
||||||
|
|
||||||
|
Spans::from(spans).into()
|
||||||
} else {
|
} else {
|
||||||
name.into()
|
name.into()
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ use helix_core::indent::IndentStyle;
|
||||||
use helix_core::{coords_at_pos, encoding, Position};
|
use helix_core::{coords_at_pos, encoding, Position};
|
||||||
use helix_lsp::lsp::DiagnosticSeverity;
|
use helix_lsp::lsp::DiagnosticSeverity;
|
||||||
use helix_view::document::DEFAULT_LANGUAGE_NAME;
|
use helix_view::document::DEFAULT_LANGUAGE_NAME;
|
||||||
|
use helix_view::icons::ICONS;
|
||||||
use helix_view::{
|
use helix_view::{
|
||||||
document::{Mode, SCRATCH_BUFFER_NAME},
|
document::{Mode, SCRATCH_BUFFER_NAME},
|
||||||
graphics::Rect,
|
graphics::Rect,
|
||||||
|
@ -15,7 +16,7 @@ use crate::ui::ProgressSpinners;
|
||||||
|
|
||||||
use helix_view::editor::StatusLineElement as StatusLineElementID;
|
use helix_view::editor::StatusLineElement as StatusLineElementID;
|
||||||
use tui::buffer::Buffer as Surface;
|
use tui::buffer::Buffer as Surface;
|
||||||
use tui::text::{Span, Spans};
|
use tui::text::{Span, Spans, ToSpan};
|
||||||
|
|
||||||
pub struct RenderContext<'a> {
|
pub struct RenderContext<'a> {
|
||||||
pub editor: &'a Editor,
|
pub editor: &'a Editor,
|
||||||
|
@ -234,29 +235,48 @@ where
|
||||||
counts
|
counts
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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 => {
|
||||||
write(context, Span::styled("●", context.editor.theme.get("hint")));
|
write(
|
||||||
write(context, format!(" {} ", hints).into());
|
context,
|
||||||
|
Span::styled(
|
||||||
|
icons.diagnostic().hint().to_string(),
|
||||||
|
context.editor.theme.get("hint"),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
write(context, Span::raw(format!(" {hints} ")));
|
||||||
}
|
}
|
||||||
Severity::Info if info > 0 => {
|
Severity::Info if info > 0 => {
|
||||||
write(context, Span::styled("●", context.editor.theme.get("info")));
|
write(
|
||||||
write(context, format!(" {} ", info).into());
|
context,
|
||||||
|
Span::styled(
|
||||||
|
icons.diagnostic().info().to_string(),
|
||||||
|
context.editor.theme.get("info"),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
write(context, Span::raw(format!(" {info} ")));
|
||||||
}
|
}
|
||||||
Severity::Warning if warnings > 0 => {
|
Severity::Warning if warnings > 0 => {
|
||||||
write(
|
write(
|
||||||
context,
|
context,
|
||||||
Span::styled("●", context.editor.theme.get("warning")),
|
Span::styled(
|
||||||
|
icons.diagnostic().warning().to_string(),
|
||||||
|
context.editor.theme.get("warning"),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
write(context, format!(" {} ", warnings).into());
|
write(context, Span::raw(format!(" {warnings} ")));
|
||||||
}
|
}
|
||||||
Severity::Error if errors > 0 => {
|
Severity::Error if errors > 0 => {
|
||||||
write(
|
write(
|
||||||
context,
|
context,
|
||||||
Span::styled("●", context.editor.theme.get("error")),
|
Span::styled(
|
||||||
|
icons.diagnostic().error().to_string(),
|
||||||
|
context.editor.theme.get("error"),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
write(context, format!(" {} ", errors).into());
|
write(context, Span::raw(format!(" {errors} ")));
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
@ -287,10 +307,10 @@ where
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
let sevs_to_show = &context.editor.config().statusline.workspace_diagnostics;
|
let sevs = &context.editor.config().statusline.workspace_diagnostics;
|
||||||
|
|
||||||
// Avoid showing the " W " if no diagnostic counts will be shown.
|
// Avoid showing the ` W ` if no diagnostic counts will be shown.
|
||||||
if !sevs_to_show.iter().any(|sev| match sev {
|
if !sevs.iter().any(|sev| match sev {
|
||||||
Severity::Hint => hints != 0,
|
Severity::Hint => hints != 0,
|
||||||
Severity::Info => info != 0,
|
Severity::Info => info != 0,
|
||||||
Severity::Warning => warnings != 0,
|
Severity::Warning => warnings != 0,
|
||||||
|
@ -299,31 +319,57 @@ where
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
write(context, " W ".into());
|
let icons = ICONS.load();
|
||||||
|
|
||||||
for sev in sevs_to_show {
|
let icon = icons.ui().workspace();
|
||||||
|
|
||||||
|
// Special case when the `workspace` key is set to `""`:
|
||||||
|
// - This will remove the default ` W ` so that the rest of the icons are spaced correctly.
|
||||||
|
if !icon.glyph().is_empty() {
|
||||||
|
write(context, icon.to_span_with(|icon| format!(" {icon} ")));
|
||||||
|
}
|
||||||
|
|
||||||
|
for sev in sevs {
|
||||||
match sev {
|
match sev {
|
||||||
Severity::Hint if hints > 0 => {
|
Severity::Hint if hints > 0 => {
|
||||||
write(context, Span::styled("●", context.editor.theme.get("hint")));
|
write(
|
||||||
write(context, format!(" {} ", hints).into());
|
context,
|
||||||
|
Span::styled(
|
||||||
|
icons.diagnostic().hint().to_string(),
|
||||||
|
context.editor.theme.get("hint"),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
write(context, Span::raw(format!(" {hints} ")));
|
||||||
}
|
}
|
||||||
Severity::Info if info > 0 => {
|
Severity::Info if info > 0 => {
|
||||||
write(context, Span::styled("●", context.editor.theme.get("info")));
|
write(
|
||||||
write(context, format!(" {} ", info).into());
|
context,
|
||||||
|
Span::styled(
|
||||||
|
format!(" {} ", icons.diagnostic().info()),
|
||||||
|
context.editor.theme.get("info"),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
write(context, Span::raw(format!(" {info} ")));
|
||||||
}
|
}
|
||||||
Severity::Warning if warnings > 0 => {
|
Severity::Warning if warnings > 0 => {
|
||||||
write(
|
write(
|
||||||
context,
|
context,
|
||||||
Span::styled("●", context.editor.theme.get("warning")),
|
Span::styled(
|
||||||
|
icons.diagnostic().warning().to_string(),
|
||||||
|
context.editor.theme.get("warning"),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
write(context, format!(" {} ", warnings).into());
|
write(context, Span::raw(format!(" {warnings} ")));
|
||||||
}
|
}
|
||||||
Severity::Error if errors > 0 => {
|
Severity::Error if errors > 0 => {
|
||||||
write(
|
write(
|
||||||
context,
|
context,
|
||||||
Span::styled("●", context.editor.theme.get("error")),
|
Span::styled(
|
||||||
|
icons.diagnostic().error().to_string(),
|
||||||
|
context.editor.theme.get("error"),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
write(context, format!(" {} ", errors).into());
|
write(context, Span::raw(format!(" {errors} ")));
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
@ -440,9 +486,18 @@ 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 file_type = context.doc.language_name().unwrap_or(DEFAULT_LANGUAGE_NAME);
|
let lang = context.doc.language_name().unwrap_or(DEFAULT_LANGUAGE_NAME);
|
||||||
|
|
||||||
write(context, format!(" {} ", file_type).into());
|
let icons = ICONS.load();
|
||||||
|
|
||||||
|
if let Some(icon) = icons
|
||||||
|
.fs()
|
||||||
|
.from_optional_path_or_lang(context.doc.path().map(|path| path.as_path()), lang)
|
||||||
|
{
|
||||||
|
write(context, icon.to_span_with(|icon| format!(" {icon} ")));
|
||||||
|
} else {
|
||||||
|
write(context, format!(" {lang} ").into());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_file_name<'a, F>(context: &mut RenderContext<'a>, write: F)
|
fn render_file_name<'a, F>(context: &mut RenderContext<'a>, write: F)
|
||||||
|
@ -539,13 +594,18 @@ fn render_version_control<'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 head = context
|
let head = context.doc.version_control_head().unwrap_or_default();
|
||||||
.doc
|
|
||||||
.version_control_head()
|
|
||||||
.unwrap_or_default()
|
|
||||||
.to_string();
|
|
||||||
|
|
||||||
write(context, head.into());
|
if !head.is_empty() {
|
||||||
|
let icons = ICONS.load();
|
||||||
|
|
||||||
|
let vcs = match icons.vcs().branch() {
|
||||||
|
Some(icon) => format!(" {icon} {head} "),
|
||||||
|
None => format!(" {head} "),
|
||||||
|
};
|
||||||
|
|
||||||
|
write(context, vcs.into());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_register<'a, F>(context: &mut RenderContext<'a>, write: F)
|
fn render_register<'a, F>(context: &mut RenderContext<'a>, write: F)
|
||||||
|
|
|
@ -9,6 +9,7 @@ use helix_view::annotations::diagnostics::{
|
||||||
DiagnosticFilter, InlineDiagnosticAccumulator, InlineDiagnosticsConfig,
|
DiagnosticFilter, InlineDiagnosticAccumulator, InlineDiagnosticsConfig,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use helix_view::icons::ICONS;
|
||||||
use helix_view::theme::Style;
|
use helix_view::theme::Style;
|
||||||
use helix_view::{Document, Theme};
|
use helix_view::{Document, Theme};
|
||||||
|
|
||||||
|
@ -102,6 +103,24 @@ impl Renderer<'_, '_> {
|
||||||
let mut end_col = start_col;
|
let mut end_col = start_col;
|
||||||
let mut draw_col = (col + 1) as u16;
|
let mut draw_col = (col + 1) as u16;
|
||||||
|
|
||||||
|
// Draw the diagnostic indicator:
|
||||||
|
if !self.renderer.column_in_bounds(draw_col as usize, 2) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let icons = ICONS.load();
|
||||||
|
|
||||||
|
let symbol = match diag.severity() {
|
||||||
|
Severity::Hint => icons.diagnostic().hint(),
|
||||||
|
Severity::Info => icons.diagnostic().info(),
|
||||||
|
Severity::Warning => icons.diagnostic().warning(),
|
||||||
|
Severity::Error => icons.diagnostic().error(),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.renderer
|
||||||
|
.set_string(self.renderer.viewport.x + draw_col, row, symbol, style);
|
||||||
|
draw_col += 2;
|
||||||
|
|
||||||
for line in diag.message.lines() {
|
for line in diag.message.lines() {
|
||||||
if !self.renderer.column_in_bounds(draw_col as usize, 1) {
|
if !self.renderer.column_in_bounds(draw_col as usize, 1) {
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -49,6 +49,7 @@
|
||||||
use helix_core::line_ending::str_is_line_ending;
|
use helix_core::line_ending::str_is_line_ending;
|
||||||
use helix_core::unicode::width::UnicodeWidthStr;
|
use helix_core::unicode::width::UnicodeWidthStr;
|
||||||
use helix_view::graphics::Style;
|
use helix_view::graphics::Style;
|
||||||
|
use helix_view::icons::Icon;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
|
@ -208,6 +209,20 @@ impl<'a> From<Cow<'a, str>> for Span<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait ToSpan<'a> {
|
||||||
|
fn to_span_with(&self, content: impl Fn(&Self) -> String) -> Span<'a>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ToSpan<'a> for Icon {
|
||||||
|
fn to_span_with(&self, content: impl Fn(&Self) -> String) -> Span<'a> {
|
||||||
|
if let Some(style) = self.color().map(|color| Style::default().fg(color)) {
|
||||||
|
Span::styled(content(self), style)
|
||||||
|
} else {
|
||||||
|
Span::raw(content(self))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A string composed of clusters of graphemes, each with their own style.
|
/// A string composed of clusters of graphemes, each with their own style.
|
||||||
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
||||||
pub struct Spans<'a>(pub Vec<Span<'a>>);
|
pub struct Spans<'a>(pub Vec<Span<'a>>);
|
||||||
|
|
|
@ -53,6 +53,7 @@ parking_lot.workspace = true
|
||||||
thiserror.workspace = true
|
thiserror.workspace = true
|
||||||
|
|
||||||
kstring = "2.0"
|
kstring = "2.0"
|
||||||
|
smartstring = { version = "1.0.1", features = ["serde"]}
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
clipboard-win = { version = "5.4", features = ["std"] }
|
clipboard-win = { version = "5.4", features = ["std"] }
|
||||||
|
|
|
@ -5,6 +5,7 @@ use helix_core::syntax::config::LanguageServerFeature;
|
||||||
use crate::{
|
use crate::{
|
||||||
editor::GutterType,
|
editor::GutterType,
|
||||||
graphics::{Style, UnderlineStyle},
|
graphics::{Style, UnderlineStyle},
|
||||||
|
icons::ICONS,
|
||||||
Document, Editor, Theme, View,
|
Document, Editor, Theme, View,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -46,7 +47,6 @@ impl GutterType {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn diagnostic<'doc>(
|
pub fn diagnostic<'doc>(
|
||||||
_editor: &'doc Editor,
|
|
||||||
doc: &'doc Document,
|
doc: &'doc Document,
|
||||||
_view: &View,
|
_view: &View,
|
||||||
theme: &Theme,
|
theme: &Theme,
|
||||||
|
@ -74,15 +74,20 @@ pub fn diagnostic<'doc>(
|
||||||
.any(|ls| ls.id() == id)
|
.any(|ls| ls.id() == id)
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
diagnostics_on_line.max_by_key(|d| d.severity).map(|d| {
|
|
||||||
write!(out, "●").ok();
|
diagnostics_on_line
|
||||||
match d.severity {
|
.max_by_key(|d| d.severity)
|
||||||
Some(Severity::Error) => error,
|
.map(move |d| {
|
||||||
Some(Severity::Warning) | None => warning,
|
let icons = ICONS.load();
|
||||||
Some(Severity::Info) => info,
|
let (style, symbol) = match d.severity {
|
||||||
Some(Severity::Hint) => hint,
|
Some(Severity::Error) => (error, icons.diagnostic().error()),
|
||||||
}
|
Some(Severity::Warning) | None => (warning, icons.diagnostic().warning()),
|
||||||
})
|
Some(Severity::Info) => (info, icons.diagnostic().info()),
|
||||||
|
Some(Severity::Hint) => (hint, icons.diagnostic().hint()),
|
||||||
|
};
|
||||||
|
out.push_str(symbol);
|
||||||
|
style
|
||||||
|
})
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -119,15 +124,17 @@ pub fn diff<'doc>(
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let icons = ICONS.load();
|
||||||
|
|
||||||
let (icon, style) = if hunk.is_pure_insertion() {
|
let (icon, style) = if hunk.is_pure_insertion() {
|
||||||
("▍", 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;
|
||||||
}
|
}
|
||||||
("▔", deleted)
|
(icons.gutter().removed(), deleted)
|
||||||
} else {
|
} else {
|
||||||
("▍", modified)
|
(icons.gutter().modified(), modified)
|
||||||
};
|
};
|
||||||
|
|
||||||
write!(out, "{}", icon).unwrap();
|
write!(out, "{}", icon).unwrap();
|
||||||
|
@ -264,7 +271,13 @@ pub fn breakpoints<'doc>(
|
||||||
breakpoint_style
|
breakpoint_style
|
||||||
};
|
};
|
||||||
|
|
||||||
let sym = if breakpoint.verified { "●" } else { "◯" };
|
let icons = ICONS.load();
|
||||||
|
|
||||||
|
let sym = if breakpoint.verified {
|
||||||
|
icons.dap().verified()
|
||||||
|
} else {
|
||||||
|
icons.dap().unverified()
|
||||||
|
};
|
||||||
write!(out, "{}", sym).unwrap();
|
write!(out, "{}", sym).unwrap();
|
||||||
Some(style)
|
Some(style)
|
||||||
},
|
},
|
||||||
|
@ -299,8 +312,9 @@ fn execution_pause_indicator<'doc>(
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let sym = "▶";
|
let icons = ICONS.load();
|
||||||
write!(out, "{}", sym).unwrap();
|
|
||||||
|
write!(out, "{}", icons.dap().play()).unwrap();
|
||||||
Some(style)
|
Some(style)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -313,7 +327,7 @@ pub fn diagnostics_or_breakpoints<'doc>(
|
||||||
theme: &Theme,
|
theme: &Theme,
|
||||||
is_focused: bool,
|
is_focused: bool,
|
||||||
) -> GutterFn<'doc> {
|
) -> GutterFn<'doc> {
|
||||||
let mut diagnostics = diagnostic(editor, doc, view, theme, is_focused);
|
let mut diagnostics = diagnostic(doc, view, theme, is_focused);
|
||||||
let mut breakpoints = breakpoints(editor, doc, view, theme, is_focused);
|
let mut breakpoints = breakpoints(editor, doc, view, theme, is_focused);
|
||||||
let mut execution_pause_indicator = execution_pause_indicator(editor, doc, theme, is_focused);
|
let mut execution_pause_indicator = execution_pause_indicator(editor, doc, theme, is_focused);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,969 @@
|
||||||
|
use arc_swap::ArcSwap;
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::{collections::HashMap, fmt::Display, path::Path};
|
||||||
|
|
||||||
|
use smartstring::{LazyCompact, SmartString};
|
||||||
|
|
||||||
|
use crate::theme::Color;
|
||||||
|
|
||||||
|
type String = SmartString<LazyCompact>;
|
||||||
|
|
||||||
|
pub static ICONS: Lazy<ArcSwap<Icons>> = Lazy::new(ArcSwap::default);
|
||||||
|
|
||||||
|
/// Centralized location for icons that can be used throughout the UI.
|
||||||
|
#[derive(Debug, Default, Deserialize, PartialEq, Eq, Clone)]
|
||||||
|
#[serde(default, deny_unknown_fields)]
|
||||||
|
pub struct Icons {
|
||||||
|
fs: Fs,
|
||||||
|
kind: Kind,
|
||||||
|
diagnostic: Diagnostic,
|
||||||
|
vcs: Vcs,
|
||||||
|
dap: Dap,
|
||||||
|
gutter: Gutter,
|
||||||
|
ui: Ui,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Icons {
|
||||||
|
#[inline]
|
||||||
|
pub fn fs(&self) -> &Fs {
|
||||||
|
&self.fs
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn kind(&self) -> &Kind {
|
||||||
|
&self.kind
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn diagnostic(&self) -> &Diagnostic {
|
||||||
|
&self.diagnostic
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn vcs(&self) -> &Vcs {
|
||||||
|
&self.vcs
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn dap(&self) -> &Dap {
|
||||||
|
&self.dap
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn gutter(&self) -> &Gutter {
|
||||||
|
&self.gutter
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn ui(&self) -> &Ui {
|
||||||
|
&self.ui
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Default, PartialEq, Eq, Clone)]
|
||||||
|
pub struct Kind {
|
||||||
|
enabled: bool,
|
||||||
|
|
||||||
|
file: Option<Icon>,
|
||||||
|
folder: Option<Icon>,
|
||||||
|
text: Option<Icon>,
|
||||||
|
module: Option<Icon>,
|
||||||
|
namespace: Option<Icon>,
|
||||||
|
package: Option<Icon>,
|
||||||
|
class: Option<Icon>,
|
||||||
|
method: Option<Icon>,
|
||||||
|
property: Option<Icon>,
|
||||||
|
field: Option<Icon>,
|
||||||
|
constructor: Option<Icon>,
|
||||||
|
#[serde(rename = "enum")]
|
||||||
|
r#enum: Option<Icon>,
|
||||||
|
interface: Option<Icon>,
|
||||||
|
function: Option<Icon>,
|
||||||
|
variable: Option<Icon>,
|
||||||
|
constant: Option<Icon>,
|
||||||
|
string: Option<Icon>,
|
||||||
|
number: Option<Icon>,
|
||||||
|
boolean: Option<Icon>,
|
||||||
|
array: Option<Icon>,
|
||||||
|
object: Option<Icon>,
|
||||||
|
key: Option<Icon>,
|
||||||
|
null: Option<Icon>,
|
||||||
|
enum_member: Option<Icon>,
|
||||||
|
#[serde(rename = "struct")]
|
||||||
|
r#struct: Option<Icon>,
|
||||||
|
event: Option<Icon>,
|
||||||
|
operator: Option<Icon>,
|
||||||
|
type_parameter: Option<Icon>,
|
||||||
|
color: Option<Icon>,
|
||||||
|
keyword: Option<Icon>,
|
||||||
|
value: Option<Icon>,
|
||||||
|
snippet: Option<Icon>,
|
||||||
|
reference: Option<Icon>,
|
||||||
|
unit: Option<Icon>,
|
||||||
|
word: Option<Icon>,
|
||||||
|
spellcheck: Option<Icon>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Kind {
|
||||||
|
#[inline]
|
||||||
|
#[must_use]
|
||||||
|
pub fn get(&self, kind: &str) -> Option<Icon> {
|
||||||
|
if !self.enabled {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let icon = match kind {
|
||||||
|
"file" => self.file()?,
|
||||||
|
"folder" => self.folder()?,
|
||||||
|
"module" => self.module()?,
|
||||||
|
"namespace" => self.namespace()?,
|
||||||
|
"package" => self.package()?,
|
||||||
|
"class" => self.class()?,
|
||||||
|
"method" => self.method()?,
|
||||||
|
"property" => self.property()?,
|
||||||
|
"field" => self.field()?,
|
||||||
|
"construct" => self.constructor()?,
|
||||||
|
"enum" => self.r#enum()?,
|
||||||
|
"interface" => self.interface()?,
|
||||||
|
"function" => self.function()?,
|
||||||
|
"variable" => self.variable()?,
|
||||||
|
"constant" => self.constant()?,
|
||||||
|
"string" => self.string()?,
|
||||||
|
"number" => self.number()?,
|
||||||
|
"boolean" => self.boolean()?,
|
||||||
|
"array" => self.array()?,
|
||||||
|
"object" => self.object()?,
|
||||||
|
"key" => self.key()?,
|
||||||
|
"null" => self.null()?,
|
||||||
|
"enum_member" => self.enum_member()?,
|
||||||
|
"struct" => self.r#struct()?,
|
||||||
|
"event" => self.event()?,
|
||||||
|
"operator" => self.operator()?,
|
||||||
|
"typeparam" => self.type_parameter()?,
|
||||||
|
"color" => self.color(),
|
||||||
|
"keyword" => self.keyword()?,
|
||||||
|
"value" => self.value()?,
|
||||||
|
"snippet" => self.snippet()?,
|
||||||
|
"reference" => self.reference()?,
|
||||||
|
"text" => self.text()?,
|
||||||
|
"unit" => self.unit()?,
|
||||||
|
"word" => self.word()?,
|
||||||
|
"spellcheck" => self.spellcheck()?,
|
||||||
|
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(icon)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn file(&self) -> Option<Icon> {
|
||||||
|
if !self.enabled {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.file.clone().or_else(|| Some(Icon::from("")))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn folder(&self) -> Option<Icon> {
|
||||||
|
if !self.enabled {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.folder.clone().or_else(|| Some(Icon::from("")))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn module(&self) -> Option<Icon> {
|
||||||
|
if !self.enabled {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.module.clone().or_else(|| Some(Icon::from("")))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn namespace(&self) -> Option<Icon> {
|
||||||
|
if !self.enabled {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.namespace.clone().or_else(|| Some(Icon::from("")))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn package(&self) -> Option<Icon> {
|
||||||
|
if !self.enabled {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.package.clone().or_else(|| Some(Icon::from("")))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn class(&self) -> Option<Icon> {
|
||||||
|
if !self.enabled {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.class.clone().or_else(|| Some(Icon::from("")))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn method(&self) -> Option<Icon> {
|
||||||
|
if !self.enabled {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.method.clone().or_else(|| Some(Icon::from("")))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn property(&self) -> Option<Icon> {
|
||||||
|
if !self.enabled {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.property.clone().or_else(|| Some(Icon::from("")))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn field(&self) -> Option<Icon> {
|
||||||
|
if !self.enabled {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.field.clone().or_else(|| Some(Icon::from("")))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn constructor(&self) -> Option<Icon> {
|
||||||
|
if !self.enabled {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.constructor.clone().or_else(|| Some(Icon::from("")))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn r#enum(&self) -> Option<Icon> {
|
||||||
|
if !self.enabled {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.r#enum.clone().or_else(|| Some(Icon::from("")))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn interface(&self) -> Option<Icon> {
|
||||||
|
if !self.enabled {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.interface.clone().or_else(|| Some(Icon::from("")))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn function(&self) -> Option<Icon> {
|
||||||
|
if !self.enabled {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.function.clone().or_else(|| Some(Icon::from("")))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn variable(&self) -> Option<Icon> {
|
||||||
|
if !self.enabled {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.variable.clone().or_else(|| Some(Icon::from("")))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn constant(&self) -> Option<Icon> {
|
||||||
|
if !self.enabled {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.constant.clone().or_else(|| Some(Icon::from("")))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn string(&self) -> Option<Icon> {
|
||||||
|
if !self.enabled {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.string.clone().or_else(|| Some(Icon::from("")))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn number(&self) -> Option<Icon> {
|
||||||
|
if !self.enabled {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.number.clone().or_else(|| Some(Icon::from("")))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn boolean(&self) -> Option<Icon> {
|
||||||
|
if !self.enabled {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.boolean.clone().or_else(|| Some(Icon::from("")))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn array(&self) -> Option<Icon> {
|
||||||
|
if !self.enabled {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.array.clone().or_else(|| Some(Icon::from("")))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn object(&self) -> Option<Icon> {
|
||||||
|
if !self.enabled {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.object.clone().or_else(|| Some(Icon::from("")))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn key(&self) -> Option<Icon> {
|
||||||
|
if !self.enabled {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.key.clone().or_else(|| Some(Icon::from("")))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn null(&self) -> Option<Icon> {
|
||||||
|
if !self.enabled {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.null.clone().or_else(|| Some(Icon::from("")))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn enum_member(&self) -> Option<Icon> {
|
||||||
|
if !self.enabled {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.enum_member.clone().or_else(|| Some(Icon::from("")))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn r#struct(&self) -> Option<Icon> {
|
||||||
|
if !self.enabled {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.r#struct.clone().or_else(|| Some(Icon::from("")))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn event(&self) -> Option<Icon> {
|
||||||
|
if !self.enabled {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.event.clone().or_else(|| Some(Icon::from("")))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn operator(&self) -> Option<Icon> {
|
||||||
|
if !self.enabled {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.operator.clone().or_else(|| Some(Icon::from("")))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn type_parameter(&self) -> Option<Icon> {
|
||||||
|
if !self.enabled {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.type_parameter
|
||||||
|
.clone()
|
||||||
|
.or_else(|| Some(Icon::from("")))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always enabled
|
||||||
|
#[inline]
|
||||||
|
pub fn color(&self) -> Icon {
|
||||||
|
self.color.clone().unwrap_or_else(|| Icon::from("■"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn keyword(&self) -> Option<Icon> {
|
||||||
|
if !self.enabled {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.keyword.clone().or_else(|| Some(Icon::from("")))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn value(&self) -> Option<Icon> {
|
||||||
|
if !self.enabled {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.value.clone().or_else(|| Some(Icon::from("")))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn snippet(&self) -> Option<Icon> {
|
||||||
|
if !self.enabled {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.snippet.clone().or_else(|| Some(Icon::from("")))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn reference(&self) -> Option<Icon> {
|
||||||
|
if !self.enabled {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.reference.clone().or_else(|| Some(Icon::from("")))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn text(&self) -> Option<Icon> {
|
||||||
|
if !self.enabled {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.text.clone().or_else(|| Some(Icon::from("")))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn unit(&self) -> Option<Icon> {
|
||||||
|
if !self.enabled {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.unit.clone().or_else(|| Some(Icon::from("")))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn word(&self) -> Option<Icon> {
|
||||||
|
if !self.enabled {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.word.clone().or_else(|| Some(Icon::from("")))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn spellcheck(&self) -> Option<Icon> {
|
||||||
|
if !self.enabled {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.spellcheck.clone().or_else(|| Some(Icon::from("")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, PartialEq, Eq, Clone, Default)]
|
||||||
|
pub struct Diagnostic {
|
||||||
|
hint: Option<String>,
|
||||||
|
info: Option<String>,
|
||||||
|
warning: Option<String>,
|
||||||
|
error: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Diagnostic {
|
||||||
|
#[inline]
|
||||||
|
pub fn hint(&self) -> &str {
|
||||||
|
self.hint.as_deref().unwrap_or("○")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn info(&self) -> &str {
|
||||||
|
self.info.as_deref().unwrap_or("●")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn warning(&self) -> &str {
|
||||||
|
self.warning.as_deref().unwrap_or("▲")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn error(&self) -> &str {
|
||||||
|
self.error.as_deref().unwrap_or("■")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, PartialEq, Eq, Clone, Default)]
|
||||||
|
pub struct Vcs {
|
||||||
|
enabled: bool,
|
||||||
|
branch: Option<String>,
|
||||||
|
added: Option<String>,
|
||||||
|
removed: Option<String>,
|
||||||
|
ignored: Option<String>,
|
||||||
|
modified: Option<String>,
|
||||||
|
renamed: Option<String>,
|
||||||
|
conflict: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Vcs {
|
||||||
|
#[inline]
|
||||||
|
pub fn branch(&self) -> Option<&str> {
|
||||||
|
if !self.enabled {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.branch.as_deref().or(Some(""))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn added(&self) -> Option<&str> {
|
||||||
|
if !self.enabled {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.added.as_deref().or(Some(""))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn removed(&self) -> Option<&str> {
|
||||||
|
if !self.enabled {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.removed.as_deref().or(Some(""))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn ignored(&self) -> Option<&str> {
|
||||||
|
if !self.enabled {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.ignored.as_deref().or(Some(""))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn modified(&self) -> Option<&str> {
|
||||||
|
if !self.enabled {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.modified.as_deref().or(Some(""))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn renamed(&self) -> Option<&str> {
|
||||||
|
if !self.enabled {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.renamed.as_deref().or(Some(""))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn conflict(&self) -> Option<&str> {
|
||||||
|
if !self.enabled {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.conflict.as_deref().or(Some(""))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, PartialEq, Eq, Clone, Default)]
|
||||||
|
pub struct Fs {
|
||||||
|
enabled: bool,
|
||||||
|
directory: Option<String>,
|
||||||
|
#[serde(rename = "directory-open")]
|
||||||
|
directory_open: Option<String>,
|
||||||
|
#[serde(flatten)]
|
||||||
|
mime: HashMap<String, Icon>,
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! mimes {
|
||||||
|
( $( $key:literal => { glyph: $glyph:expr $(, color: $color:expr)? } ),* $(,)? ) => {{
|
||||||
|
let mut map = HashMap::new();
|
||||||
|
$(
|
||||||
|
map.insert(String::from($key), Icon {
|
||||||
|
glyph: String::from($glyph),
|
||||||
|
color: None $(.or( Some(Color::from_hex($color).unwrap())) )?,
|
||||||
|
});
|
||||||
|
)*
|
||||||
|
map
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
static MIMES: once_cell::sync::Lazy<HashMap<String, Icon>> = once_cell::sync::Lazy::new(|| {
|
||||||
|
mimes! {
|
||||||
|
// Language name
|
||||||
|
"git-commit" => {glyph: "", color: "#f15233" },
|
||||||
|
"git-rebase" => {glyph: "", color: "#f15233" },
|
||||||
|
"git-config" => {glyph: "", color: "#f15233" },
|
||||||
|
"helm" => {glyph: "", color: "#277a9f" },
|
||||||
|
"nginx" => {glyph: "", color: "#019639" },
|
||||||
|
"text" => { glyph: "" },
|
||||||
|
|
||||||
|
// Exact
|
||||||
|
"README.md" => { glyph: "" },
|
||||||
|
"LICENSE" => { glyph: "", color: "#e7a933" },
|
||||||
|
"CHANGELOG.md" => { glyph: "", color: "#7bab43" },
|
||||||
|
".gitignore" => { glyph: "", color: "#f15233" },
|
||||||
|
".gitattributes" => { glyph: "", color: "#f15233" },
|
||||||
|
".editorconfig" => { glyph: "" },
|
||||||
|
".env" => { glyph: "" },
|
||||||
|
".dockerignore" => {glyph: "", color: "#0096e6" },
|
||||||
|
".ignore" => {glyph: "" },
|
||||||
|
"docker-compose.yaml" => {glyph: "", color: "#0096e6" },
|
||||||
|
"compose.yaml" => {glyph: "", color: "#0096e6" },
|
||||||
|
"Makefile" => {glyph: "" },
|
||||||
|
"Dockerfile" => {glyph: "", color: "#0096e6" },
|
||||||
|
|
||||||
|
// Extension
|
||||||
|
"rs" => {glyph: "", color: "#fab387" },
|
||||||
|
"py" => {glyph: "", color: "#ffd94a" },
|
||||||
|
"c" => {glyph: "", color: "#b0c4de" },
|
||||||
|
"cpp" => {glyph: "", color: "#0288d1" },
|
||||||
|
"cs" => {glyph: "", color: "#512bd4" },
|
||||||
|
"d" => {glyph: "", color: "#b03931" },
|
||||||
|
"ex" => {glyph: "", color: "#71567d" },
|
||||||
|
"fs" => {glyph: "", color: "#2fb9da" },
|
||||||
|
"go" => {glyph: "", color: "#00acd8" },
|
||||||
|
"hs" => {glyph: "", color: "#5e5089" },
|
||||||
|
"java" => {glyph: "", color: "#f58217" },
|
||||||
|
"js" => {glyph: "", color: "#f0dc4e" },
|
||||||
|
"ts" => {glyph: "", color: "#3179c7" },
|
||||||
|
"kt" => {glyph: "", color: "#8a48fc" },
|
||||||
|
"html" => {glyph: "", color: "#f15c29" },
|
||||||
|
"css" => {glyph: "", color: "#9479b6" },
|
||||||
|
"scss" => {glyph: "", color: "#d06599" },
|
||||||
|
"sh" => {glyph: "" },
|
||||||
|
"bash" => {glyph: "" },
|
||||||
|
"php" => {glyph: "", color: "#777bb3" },
|
||||||
|
"ps1" => {glyph: "", color: "#2670be" },
|
||||||
|
"dart" => {glyph: "", color: "#2db7f6" },
|
||||||
|
"ruby" => {glyph: "", color: "#d30000" },
|
||||||
|
"swift" => {glyph: "", color: "#fba03d" },
|
||||||
|
"r" => {glyph: "", color: "#236abd" },
|
||||||
|
"groovy" => {glyph: "", color: "#4298b8" },
|
||||||
|
"scala" => {glyph: "", color: "#db3331" },
|
||||||
|
"pl" => {glyph: "", color: "#006894" },
|
||||||
|
"clj" => {glyph: "", color: "#91b4ff" },
|
||||||
|
"jl" => {glyph: "", color: "#cb3c33" },
|
||||||
|
"zig" => {glyph: "", color: "#f7a41d" },
|
||||||
|
"f" => {glyph: "", color: "#734f96" },
|
||||||
|
"erl" => {glyph: "", color: "#a90432" },
|
||||||
|
"ml" => {glyph: "", color: "#f29000" },
|
||||||
|
"cr" => {glyph: "" },
|
||||||
|
"svelte" => {glyph: "", color: "#ff5620" },
|
||||||
|
"gd" => {glyph: "", color: "#478cbf" },
|
||||||
|
"nim" => {glyph: "", color: "#efc743" },
|
||||||
|
"jsx" => {glyph: "", color: "#61dafb" },
|
||||||
|
"tsx" => {glyph: "", color: "#61dafb" },
|
||||||
|
"twig" => {glyph: "", color: "#a8bf21" },
|
||||||
|
"lua" => {glyph: "", color: "#74c7ec" },
|
||||||
|
"vue" => {glyph: "", color: "#40b884" },
|
||||||
|
"lisp" => {glyph: "" },
|
||||||
|
"elm" => {glyph: "", color: "#5b6379" },
|
||||||
|
"res" => {glyph: "", color: "#ef5350" },
|
||||||
|
"sol" => {glyph: "" },
|
||||||
|
"vala" => {glyph: "", color: "#a972e4" },
|
||||||
|
"scm" => {glyph: "", color: "#d53d32" },
|
||||||
|
"v" => {glyph: "", color: "#5e87c0" },
|
||||||
|
"prisma" => {glyph: "" },
|
||||||
|
"ada" => {glyph: "", color: "#195c19" },
|
||||||
|
"astro" => {glyph: "", color: "#ed45cf" },
|
||||||
|
"m" => {glyph: "", color: "#ed8012" },
|
||||||
|
"rst" => {glyph: "", color: "#74aada" },
|
||||||
|
"cl" => {glyph: "" },
|
||||||
|
"njk" => {glyph: "", color: "#53a553" },
|
||||||
|
"jinja" => {glyph: "" },
|
||||||
|
"bicep" => {glyph: "", color: "#529ab7" },
|
||||||
|
"wat" => {glyph: "", color: "#644fef" },
|
||||||
|
"md" => {glyph: "" },
|
||||||
|
"make" => {glyph: "" },
|
||||||
|
"cmake" => {glyph: "", color: "#3eae2b" },
|
||||||
|
"nix" => {glyph: "", color: "#4f73bd" },
|
||||||
|
"awk" => {glyph: "" },
|
||||||
|
"ll" => {glyph: "", color: "#09627d" },
|
||||||
|
"regex" => {glyph: "" },
|
||||||
|
"gql" => {glyph: "", color: "#e534ab" },
|
||||||
|
"typst" => {glyph: "", color: "#5bc0af" },
|
||||||
|
"json" => {glyph: "" },
|
||||||
|
"toml" => {glyph: "", color: "#a8403e" },
|
||||||
|
"xml" => {glyph: "", color: "#8bc34a" },
|
||||||
|
"tex" => {glyph: "", color: "#008080" },
|
||||||
|
"todotxt" => {glyph: "", color: "#7cb342" },
|
||||||
|
"svg" => {glyph: "", color: "#ffb300" },
|
||||||
|
"png" => {glyph: "", color: "#26a69a" },
|
||||||
|
"jpeg" => {glyph: "", color: "#26a69a" },
|
||||||
|
"jpg" => {glyph: "", color: "#26a69a" },
|
||||||
|
"lock" => {glyph: "", color: "#70797d" },
|
||||||
|
"csv" => {glyph: "", color: "#1abb54" },
|
||||||
|
"ipynb" => {glyph: "", color: "#f47724" },
|
||||||
|
"ttf" => {glyph: "", color: "#144cb7" },
|
||||||
|
"exe" => {glyph: "" },
|
||||||
|
"bin" => {glyph: "" },
|
||||||
|
"bzl" => {glyph: "", color: "#76d275" },
|
||||||
|
"sql" => {glyph: "" },
|
||||||
|
"db" => {glyph: "" },
|
||||||
|
"yaml" => { glyph: "", color: "#cc1018" },
|
||||||
|
"yml" => { glyph: "", color: "#cc1018" },
|
||||||
|
"conf" => { glyph: "" },
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
impl Fs {
|
||||||
|
#[inline]
|
||||||
|
pub fn directory(&self, is_open: bool) -> Option<&str> {
|
||||||
|
if !self.enabled {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let dir = if is_open {
|
||||||
|
self.directory_open.as_deref().unwrap_or("")
|
||||||
|
} else {
|
||||||
|
self.directory.as_deref().unwrap_or("")
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn from_name<'a>(&'a self, name: &str) -> Option<&'a Icon> {
|
||||||
|
if !self.enabled {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.mime.get(name).or_else(|| MIMES.get(name))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn from_path<'b, 'a: 'b>(&'a self, path: &'b Path) -> Option<&'b Icon> {
|
||||||
|
self.__from_path_or_lang(Some(path), None)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn from_optional_path<'b, 'a: 'b>(&'a self, path: Option<&'b Path>) -> Option<&'b Icon> {
|
||||||
|
self.__from_path_or_lang(path, None)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn from_path_or_lang<'b, 'a: 'b>(
|
||||||
|
&'a self,
|
||||||
|
path: &'b Path,
|
||||||
|
lang: &'b str,
|
||||||
|
) -> Option<&'b Icon> {
|
||||||
|
self.__from_path_or_lang(Some(path), Some(lang))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn from_optional_path_or_lang<'b, 'a: 'b>(
|
||||||
|
&'a self,
|
||||||
|
path: Option<&'b Path>,
|
||||||
|
lang: &'b str,
|
||||||
|
) -> Option<&'b Icon> {
|
||||||
|
self.__from_path_or_lang(path, Some(lang))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn __from_path_or_lang<'b, 'a: 'b>(
|
||||||
|
&'a self,
|
||||||
|
path: Option<&'b Path>,
|
||||||
|
lang: Option<&'b str>,
|
||||||
|
) -> Option<&'b Icon> {
|
||||||
|
if !self.enabled {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(path) = path {
|
||||||
|
// Search for fully specified name first so that custom icons,
|
||||||
|
// for example for `README.md` or `docker-compose.yaml`, can
|
||||||
|
// take precedence over any extension it may have.
|
||||||
|
if let Some(Some(name)) = path.file_name().map(|name| name.to_str()) {
|
||||||
|
// Search config options first, then built-in.
|
||||||
|
if let Some(icon) = self.mime.get(name).or_else(|| MIMES.get(name)) {
|
||||||
|
return Some(icon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to search for icons based off of the extension.
|
||||||
|
if let Some(Some(ext)) = path.extension().map(|ext| ext.to_str()) {
|
||||||
|
// Search config options first, then built-in.
|
||||||
|
if let Some(icon) = self.mime.get(ext).or_else(|| MIMES.get(ext)) {
|
||||||
|
return Some(icon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to search via lang name.
|
||||||
|
if let Some(lang) = lang {
|
||||||
|
// Search config options first, then built-in.
|
||||||
|
if let Some(icon) = self.mime.get(lang).or_else(|| MIMES.get(lang)) {
|
||||||
|
return Some(icon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If icons are enabled but there is no matching found, default to the `text` icon.
|
||||||
|
// Check user configured first, then built-in.
|
||||||
|
self.mime.get("text").or_else(|| MIMES.get("text"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Default)]
|
||||||
|
pub struct Dap {
|
||||||
|
verified: Option<String>,
|
||||||
|
unverified: Option<String>,
|
||||||
|
play: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Dap {
|
||||||
|
#[inline]
|
||||||
|
pub fn verified(&self) -> &str {
|
||||||
|
self.verified.as_deref().unwrap_or("●")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn unverified(&self) -> &str {
|
||||||
|
self.unverified.as_deref().unwrap_or("◯")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn play(&self) -> &str {
|
||||||
|
self.play.as_deref().unwrap_or("▶")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Default)]
|
||||||
|
pub struct Gutter {
|
||||||
|
added: Option<String>,
|
||||||
|
modified: Option<String>,
|
||||||
|
removed: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Gutter {
|
||||||
|
#[inline]
|
||||||
|
pub fn added(&self) -> &str {
|
||||||
|
self.added.as_deref().unwrap_or("▍")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn modified(&self) -> &str {
|
||||||
|
self.modified.as_deref().unwrap_or("▍")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn removed(&self) -> &str {
|
||||||
|
self.removed.as_deref().unwrap_or("▔")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, PartialEq, Eq, Clone)]
|
||||||
|
pub struct Icon {
|
||||||
|
glyph: String,
|
||||||
|
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 {
|
||||||
|
pub fn glyph(&self) -> &str {
|
||||||
|
self.glyph.as_str()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn color(&self) -> Option<Color> {
|
||||||
|
self.color
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&str> for Icon {
|
||||||
|
fn from(icon: &str) -> Self {
|
||||||
|
Self {
|
||||||
|
glyph: String::from(icon),
|
||||||
|
color: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Icon {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.glyph)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for Icon {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
deserializer.deserialize_any(IconVisitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct IconVisitor;
|
||||||
|
|
||||||
|
impl<'de> serde::de::Visitor<'de> for IconVisitor {
|
||||||
|
type Value = Icon;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
formatter,
|
||||||
|
"a string glyph or a map with 'glyph' and optional 'color'"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_str<E>(self, glyph: &str) -> Result<Self::Value, E>
|
||||||
|
where
|
||||||
|
E: serde::de::Error,
|
||||||
|
{
|
||||||
|
Ok(Icon {
|
||||||
|
glyph: String::from(glyph),
|
||||||
|
color: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
|
||||||
|
where
|
||||||
|
M: serde::de::MapAccess<'de>,
|
||||||
|
{
|
||||||
|
let mut glyph = None;
|
||||||
|
let mut color = None;
|
||||||
|
|
||||||
|
while let Some(key) = map.next_key::<String>()? {
|
||||||
|
match key.as_str() {
|
||||||
|
"glyph" => {
|
||||||
|
if glyph.is_some() {
|
||||||
|
return Err(serde::de::Error::duplicate_field("glyph"));
|
||||||
|
}
|
||||||
|
glyph = Some(map.next_value::<String>()?);
|
||||||
|
}
|
||||||
|
"color" => {
|
||||||
|
if color.is_some() {
|
||||||
|
return Err(serde::de::Error::duplicate_field("color"));
|
||||||
|
}
|
||||||
|
color = Some(map.next_value::<String>()?);
|
||||||
|
}
|
||||||
|
_ => return Err(serde::de::Error::unknown_field(&key, &["glyph", "color"])),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let glyph = glyph.ok_or_else(|| serde::de::Error::missing_field("glyph"))?;
|
||||||
|
|
||||||
|
let color = if let Some(hex) = color {
|
||||||
|
let color = Color::from_hex(&hex).ok_or_else(|| {
|
||||||
|
serde::de::Error::custom(format!("`{hex} is not a valid color code`"))
|
||||||
|
})?;
|
||||||
|
Some(color)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Icon { glyph, color })
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,6 +11,7 @@ pub mod expansion;
|
||||||
pub mod graphics;
|
pub mod graphics;
|
||||||
pub mod gutter;
|
pub mod gutter;
|
||||||
pub mod handlers;
|
pub mod handlers;
|
||||||
|
pub mod icons;
|
||||||
pub mod info;
|
pub mod info;
|
||||||
pub mod input;
|
pub mod input;
|
||||||
pub mod keyboard;
|
pub mod keyboard;
|
||||||
|
|
Loading…
Reference in New Issue