This commit is contained in:
RoloEdits 2025-04-03 08:20:59 +00:00 committed by GitHub
commit 2f81c64a46
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 1288 additions and 42 deletions

2
Cargo.lock generated
View file

@ -1560,6 +1560,7 @@ dependencies = [
"serde",
"serde_json",
"slotmap",
"smartstring",
"tempfile",
"thiserror 2.0.12",
"tokio",
@ -2423,6 +2424,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29"
dependencies = [
"autocfg",
"serde",
"static_assertions",
"version_check",
]

View file

@ -44,6 +44,7 @@ use helix_core::{
use helix_view::{
document::{FormatterError, Mode, SCRATCH_BUFFER_NAME},
editor::Action,
icons::ICONS,
info::Info,
input::KeyEvent,
keyboard::KeyCode,
@ -3296,12 +3297,28 @@ fn changed_file_picker(cx: &mut Context) {
let columns = [
PickerColumn::new("change", |change: &FileChange, data: &FileChangeData| {
let icons = ICONS.load();
match change {
FileChange::Untracked { .. } => Span::styled("+ untracked", data.style_untracked),
FileChange::Modified { .. } => Span::styled("~ modified", data.style_modified),
FileChange::Conflict { .. } => Span::styled("x conflict", data.style_conflict),
FileChange::Deleted { .. } => Span::styled("- deleted", data.style_deleted),
FileChange::Renamed { .. } => Span::styled("> renamed", data.style_renamed),
FileChange::Untracked { .. } => Span::styled(
format!("{} untracked", icons.vcs().added()),
data.style_untracked,
),
FileChange::Modified { .. } => Span::styled(
format!("{} modified", icons.vcs().modified()),
data.style_modified,
),
FileChange::Conflict { .. } => Span::styled(
format!("{} conflict", icons.vcs().conflict()),
data.style_conflict,
),
FileChange::Deleted { .. } => Span::styled(
format!("{} deleted", icons.vcs().removed()),
data.style_deleted,
),
FileChange::Renamed { .. } => Span::styled(
format!("{} renamed", icons.vcs().renamed()),
data.style_renamed,
),
}
.into()
}),

View file

@ -22,6 +22,7 @@ use helix_view::{
document::{DocumentInlayHints, DocumentInlayHintsId},
editor::Action,
handlers::lsp::SignatureHelpInvoked,
icons::ICONS,
theme::Style,
Document, View,
};
@ -182,7 +183,7 @@ fn display_symbol_kind(kind: lsp::SymbolKind) -> &'static str {
lsp::SymbolKind::OBJECT => "object",
lsp::SymbolKind::KEY => "key",
lsp::SymbolKind::NULL => "null",
lsp::SymbolKind::ENUM_MEMBER => "enummem",
lsp::SymbolKind::ENUM_MEMBER => "enum_member",
lsp::SymbolKind::STRUCT => "struct",
lsp::SymbolKind::EVENT => "event",
lsp::SymbolKind::OPERATOR => "operator",
@ -242,11 +243,22 @@ fn diag_picker(
ui::PickerColumn::new(
"severity",
|item: &PickerDiagnostic, styles: &DiagnosticStyles| {
let icons = ICONS.load();
match item.diag.severity {
Some(DiagnosticSeverity::HINT) => Span::styled("HINT", styles.hint),
Some(DiagnosticSeverity::INFORMATION) => Span::styled("INFO", styles.info),
Some(DiagnosticSeverity::WARNING) => Span::styled("WARN", styles.warning),
Some(DiagnosticSeverity::ERROR) => Span::styled("ERROR", styles.error),
Some(DiagnosticSeverity::HINT) => {
Span::styled(format!("{} HINT", icons.diagnostic().hint()), styles.hint)
}
Some(DiagnosticSeverity::INFORMATION) => {
Span::styled(format!("{} INFO", icons.diagnostic().info()), styles.info)
}
Some(DiagnosticSeverity::WARNING) => Span::styled(
format!("{} WARN", icons.diagnostic().warning()),
styles.warning,
),
Some(DiagnosticSeverity::ERROR) => Span::styled(
format!("{} ERROR", icons.diagnostic().error()),
styles.error,
),
_ => Span::raw(""),
}
.into()
@ -397,7 +409,22 @@ pub fn symbol_picker(cx: &mut Context) {
let call = move |_editor: &mut Editor, compositor: &mut Compositor| {
let columns = [
ui::PickerColumn::new("kind", |item: &SymbolInformationItem, _| {
display_symbol_kind(item.symbol.kind).into()
let icons = ICONS.load();
let name = display_symbol_kind(item.symbol.kind);
if let Some(icon) = icons.kind().get(name) {
if let Some(color) = icon.color() {
Span::styled(
format!("{} {name}", icon.glyph()),
Style::default().fg(color),
)
.into()
} else {
format!("{} {name}", icon.glyph()).into()
}
} else {
name.into()
}
}),
// Some symbols in the document symbol picker may have a URI that isn't
// the current file. It should be rare though, so we concatenate that
@ -515,7 +542,22 @@ pub fn workspace_symbol_picker(cx: &mut Context) {
};
let columns = [
ui::PickerColumn::new("kind", |item: &SymbolInformationItem, _| {
display_symbol_kind(item.symbol.kind).into()
let icons = ICONS.load();
let name = display_symbol_kind(item.symbol.kind);
if let Some(icon) = icons.kind().get(name) {
if let Some(color) = icon.color() {
Span::styled(
format!("{} {name}", icon.glyph()),
Style::default().fg(color),
)
.into()
} else {
format!("{} {name}", icon.glyph()).into()
}
} else {
name.into()
}
}),
ui::PickerColumn::new("name", |item: &SymbolInformationItem, _| {
item.symbol.name.as_str().into()

View file

@ -2,11 +2,13 @@ use crate::keymap;
use crate::keymap::{merge_keys, KeyTrie};
use helix_loader::merge_toml_values;
use helix_view::document::Mode;
use helix_view::icons::{Icons, ICONS};
use serde::Deserialize;
use std::collections::HashMap;
use std::fmt::Display;
use std::fs;
use std::io::Error as IOError;
use std::sync::Arc;
use toml::de::Error as TomlError;
#[derive(Debug, Clone, PartialEq)]
@ -22,6 +24,7 @@ pub struct ConfigRaw {
pub theme: Option<String>,
pub keys: Option<HashMap<Mode, KeyTrie>>,
pub editor: Option<toml::Value>,
pub icons: Option<toml::Value>,
}
impl Default for Config {
@ -64,6 +67,7 @@ impl Config {
global.and_then(|file| toml::from_str(&file).map_err(ConfigLoadError::BadConfig));
let local_config: Result<ConfigRaw, ConfigLoadError> =
local.and_then(|file| toml::from_str(&file).map_err(ConfigLoadError::BadConfig));
let res = match (global_config, local_config) {
(Ok(global), Ok(local)) => {
let mut keys = keymap::default();
@ -84,6 +88,18 @@ impl Config {
.map_err(ConfigLoadError::BadConfig)?,
};
let icons: Icons = match (global.icons, local.icons) {
(None, None) => Icons::default(),
(None, Some(val)) | (Some(val), None) => {
val.try_into().map_err(ConfigLoadError::BadConfig)?
}
(Some(global), Some(local)) => merge_toml_values(global, local, 3)
.try_into()
.map_err(ConfigLoadError::BadConfig)?,
};
ICONS.store(Arc::new(icons));
Config {
theme: local.theme.or(global.theme),
keys,
@ -100,6 +116,14 @@ impl Config {
if let Some(keymap) = config.keys {
merge_keys(&mut keys, keymap);
}
let icons = config.icons.map_or_else(
|| Ok(Icons::default()),
|val| val.try_into().map_err(ConfigLoadError::BadConfig),
)?;
ICONS.store(Arc::new(icons));
Config {
theme: config.theme,
keys,

View file

@ -8,6 +8,7 @@ use helix_view::{
document::DocumentColorSwatches,
events::{DocumentDidChange, DocumentDidOpen, LanguageServerExited, LanguageServerInitialized},
handlers::{lsp::DocumentColorsEvent, Handlers},
icons::ICONS,
DocumentId, Editor, Theme,
};
use tokio::time::Instant;
@ -120,9 +121,11 @@ fn attach_document_colors(
let mut color_swatches_padding = Vec::with_capacity(doc_colors.len());
let mut colors = Vec::with_capacity(doc_colors.len());
let icons = ICONS.load();
for (pos, color) in doc_colors {
color_swatches_padding.push(InlineAnnotation::new(pos, " "));
color_swatches.push(InlineAnnotation::new(pos, ""));
color_swatches.push(InlineAnnotation::new(pos, icons.kind().color().glyph()));
colors.push(Theme::rgb_highlight(
(color.red * 255.) as u8,
(color.green * 255.) as u8,

View file

@ -9,6 +9,7 @@ use crate::{
use helix_core::snippets::{ActiveSnippet, RenderedSnippet, Snippet};
use helix_core::{self as core, chars, fuzzy::MATCHER, Change, Transaction};
use helix_lsp::{lsp, util, OffsetEncoding};
use helix_view::icons::ICONS;
use helix_view::{
editor::CompleteAction,
handlers::lsp::SignatureHelpInvoked,
@ -45,7 +46,7 @@ impl menu::Item for CompletionItem {
CompletionItem::Other(core::CompletionItem { label, .. }) => label,
};
let kind = match self {
let mut kind = match self {
CompletionItem::Lsp(LspCompletionItem { item, .. }) => match item.kind {
Some(lsp::CompletionItemKind::TEXT) => "text".into(),
Some(lsp::CompletionItemKind::METHOD) => "method".into(),
@ -78,9 +79,13 @@ impl menu::Item for CompletionItem {
})
.and_then(Color::from_hex)
.map_or("color".into(), |color| {
let icons = ICONS.load();
Spans::from(vec![
Span::raw("color "),
Span::styled("", Style::default().fg(color)),
Span::styled(
icons.kind().color().glyph().to_string(),
Style::default().fg(color),
),
])
}),
Some(lsp::CompletionItemKind::FILE) => "file".into(),
@ -101,6 +106,20 @@ impl menu::Item for CompletionItem {
CompletionItem::Other(core::CompletionItem { kind, .. }) => kind.as_ref().into(),
};
let icons = ICONS.load();
let name = &kind.0[0].content;
if let Some(icon) = icons.kind().get(name) {
if let Some(color) = icon.color() {
kind.0[0].content = format!("{} {name}", icon.glyph()).into();
kind.0[0].style = Style::default().fg(color);
} else {
kind.0[0].content = format!("{} {name}", icon.glyph()).into();
}
} else {
kind.0[0].content = format!("{name}").into();
}
let label = Span::styled(
label,
if deprecated {

View file

@ -27,6 +27,7 @@ use helix_view::{
document::{Mode, SCRATCH_BUFFER_NAME},
editor::{CompleteAction, CursorShapeConfig},
graphics::{Color, CursorKind, Modifier, Rect, Style},
icons::ICONS,
input::{KeyEvent, MouseButton, MouseEvent, MouseEventKind},
keyboard::{KeyCode, KeyModifiers},
Document, Editor, Theme, View,
@ -647,7 +648,19 @@ impl EditorView {
bufferline_inactive
};
let text = format!(" {}{} ", fname, if doc.is_modified() { "[+]" } else { "" });
let icons = ICONS.load();
let text = if let Some(icon) = icons.mime().get(doc.path(), doc.language_name()) {
format!(
" {} {} {}",
icon.glyph(),
fname,
if doc.is_modified() { "[+] " } else { "" }
)
} else {
format!(" {} {}", fname, if doc.is_modified() { "[+] " } else { "" })
};
let used_width = viewport.x.saturating_sub(x);
let rem_width = surface.area.width.saturating_sub(used_width);

View file

@ -20,6 +20,7 @@ use crate::job::{self, Callback};
pub use completion::Completion;
pub use editor::EditorView;
use helix_stdx::rope;
use helix_view::icons::ICONS;
use helix_view::theme::Style;
pub use markdown::Markdown;
pub use menu::Menu;
@ -249,7 +250,21 @@ pub fn file_picker(editor: &Editor, root: PathBuf) -> FilePicker {
"path",
|item: &PathBuf, data: &FilePickerData| {
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.mime().get(Some(&path.to_path_buf()), None) {
if let Some(color) = icon.color() {
spans.push(Span::styled(
format!("{} ", icon.glyph()),
Style::default().fg(color),
));
} else {
spans.push(Span::raw(format!("{} ", icon.glyph())));
}
}
if let Some(dirs) = path.parent().filter(|p| !p.as_os_str().is_empty()) {
spans.extend([
Span::styled(dirs.to_string_lossy(), data.directory_style),
@ -310,8 +325,27 @@ pub fn file_explorer(root: PathBuf, editor: &Editor) -> Result<FileExplorer, std
"path",
|(path, is_dir): &(PathBuf, bool), (root, directory_style): &(PathBuf, Style)| {
let name = path.strip_prefix(root).unwrap_or(path).to_string_lossy();
let icons = ICONS.load();
if *is_dir {
Span::styled(format!("{}/", name), *directory_style).into()
if let Some(icon) = icons.mime().directory() {
Span::styled(format!("{icon} {name}/"), *directory_style).into()
} else {
Span::styled(format!("{name}/"), *directory_style).into()
}
} else if let Some(icon) = icons.mime().get(Some(path), None).cloned() {
let mut spans = Vec::with_capacity(2);
if let Some(color) = icon.color() {
let icon =
Span::styled(format!("{} ", icon.glyph()), Style::default().fg(color));
let filename = Span::raw(name);
spans.push(icon);
spans.push(filename);
} else {
spans.push(Span::raw(format!("{} ", icon.glyph())));
spans.push(Span::raw(name));
}
Spans::from(spans).into()
} else {
name.into()
}

View file

@ -1,6 +1,7 @@
use helix_core::{coords_at_pos, encoding, Position};
use helix_lsp::lsp::DiagnosticSeverity;
use helix_view::document::DEFAULT_LANGUAGE_NAME;
use helix_view::icons::ICONS;
use helix_view::{
document::{Mode, SCRATCH_BUFFER_NAME},
graphics::Rect,
@ -240,10 +241,12 @@ where
counts
});
let icons = ICONS.load();
if warnings > 0 {
write(
context,
"".to_string(),
icons.diagnostic().warning().to_string(),
Some(context.editor.theme.get("warning")),
);
write(context, format!(" {} ", warnings), None);
@ -252,7 +255,7 @@ where
if errors > 0 {
write(
context,
"".to_string(),
icons.diagnostic().error().to_string(),
Some(context.editor.theme.get("error")),
);
write(context, format!(" {} ", errors), None);
@ -282,10 +285,12 @@ where
write(context, " W ".into(), None);
}
let icons = ICONS.load();
if warnings > 0 {
write(
context,
"".to_string(),
icons.diagnostic().warning().to_string(),
Some(context.editor.theme.get("warning")),
);
write(context, format!(" {} ", warnings), None);
@ -294,7 +299,7 @@ where
if errors > 0 {
write(
context,
"".to_string(),
icons.diagnostic().error().to_string(),
Some(context.editor.theme.get("error")),
);
write(context, format!(" {} ", errors), None);
@ -410,9 +415,31 @@ fn render_file_type<F>(context: &mut RenderContext, write: F)
where
F: Fn(&mut RenderContext, String, Option<Style>) + Copy,
{
let file_type = context.doc.language_name().unwrap_or(DEFAULT_LANGUAGE_NAME);
let icons = ICONS.load();
write(context, format!(" {} ", file_type), None);
if let Some(icon) = icons
.mime()
.get(context.doc.path(), context.doc.language_name())
{
if let Some(color) = icon.color() {
write(
context,
format!(" {} ", icon.glyph()),
Some(Style::default().fg(color)),
);
} else {
write(context, format!(" {} ", icon.glyph()), None);
}
} else {
write(
context,
format!(
" {} ",
context.doc.language_name().unwrap_or(DEFAULT_LANGUAGE_NAME)
),
None,
);
}
}
fn render_file_name<F>(context: &mut RenderContext, write: F)
@ -514,13 +541,17 @@ fn render_version_control<F>(context: &mut RenderContext, write: F)
where
F: Fn(&mut RenderContext, String, Option<Style>) + Copy,
{
let head = context
.doc
.version_control_head()
.unwrap_or_default()
.to_string();
let head = context.doc.version_control_head().unwrap_or_default();
write(context, head, None);
let vcs = if head.is_empty() {
format!("{head}")
} else {
let icons = ICONS.load();
let icon = icons.vcs().branch();
format!(" {icon} {head}")
};
write(context, vcs, None);
}
fn render_register<F>(context: &mut RenderContext, write: F)

View file

@ -9,6 +9,7 @@ use helix_view::annotations::diagnostics::{
DiagnosticFilter, InlineDiagnosticAccumulator, InlineDiagnosticsConfig,
};
use helix_view::icons::ICONS;
use helix_view::theme::Style;
use helix_view::{Document, Theme};
@ -102,6 +103,24 @@ impl Renderer<'_, '_> {
let mut end_col = start_col;
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() {
if !self.renderer.column_in_bounds(draw_col as usize, 1) {
break;

View file

@ -52,6 +52,8 @@ log = "~0.4"
parking_lot.workspace = true
thiserror.workspace = true
smartstring = { version = "1.0.1", features = ["serde"]}
[target.'cfg(windows)'.dependencies]
clipboard-win = { version = "5.4", features = ["std"] }

View file

@ -5,6 +5,7 @@ use helix_core::syntax::LanguageServerFeature;
use crate::{
editor::GutterType,
graphics::{Style, UnderlineStyle},
icons::ICONS,
Document, Editor, Theme, View,
};
@ -46,7 +47,6 @@ impl GutterType {
}
pub fn diagnostic<'doc>(
_editor: &'doc Editor,
doc: &'doc Document,
_view: &View,
theme: &Theme,
@ -74,15 +74,20 @@ pub fn diagnostic<'doc>(
.any(|ls| ls.id() == id)
})
});
diagnostics_on_line.max_by_key(|d| d.severity).map(|d| {
write!(out, "").ok();
match d.severity {
Some(Severity::Error) => error,
Some(Severity::Warning) | None => warning,
Some(Severity::Info) => info,
Some(Severity::Hint) => hint,
}
})
diagnostics_on_line
.max_by_key(|d| d.severity)
.map(move |d| {
let icons = ICONS.load();
let (style, symbol) = match d.severity {
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
})
},
)
}
@ -264,7 +269,13 @@ pub fn breakpoints<'doc>(
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();
Some(style)
},
@ -313,7 +324,7 @@ pub fn diagnostics_or_breakpoints<'doc>(
theme: &Theme,
is_focused: bool,
) -> 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 execution_pause_indicator = execution_pause_indicator(editor, doc, theme, is_focused);

1028
helix-view/src/icons.rs Normal file

File diff suppressed because it is too large Load diff

View file

@ -11,6 +11,7 @@ pub mod expansion;
pub mod graphics;
pub mod gutter;
pub mod handlers;
pub mod icons;
pub mod info;
pub mod input;
pub mod keyboard;