Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: icon support #2869

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
1 change: 1 addition & 0 deletions book/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
- [Migrating from Vim](./from-vim.md)
- [Configuration](./configuration.md)
- [Themes](./themes.md)
- [Icons](./icons.md)
- [Key remapping](./remapping.md)
- [Languages](./languages.md)
- [Guides](./guides/README.md)
Expand Down
14 changes: 14 additions & 0 deletions book/src/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Example config:

```toml
theme = "onedark"
icons = "nerdfonts"

[editor]
line-number = "relative"
Expand Down Expand Up @@ -108,6 +109,7 @@ The following statusline elements can be configured:
| `file-line-ending` | The file line endings (CRLF or LF) |
| `total-line-numbers` | The total line numbers of the opened file |
| `file-type` | The type of the opened file |
| `file-type-icon` | The icon representing the language of the open file, or else its file type (see `[editor.icons]` section) |
| `diagnostics` | The number of warnings and/or errors |
| `workspace-diagnostics` | The number of warnings and/or errors on workspace |
| `selections` | The number of active selections |
Expand Down Expand Up @@ -323,6 +325,18 @@ Currently unused

Currently unused

### `[editor.icons]` Section

Option for displaying icons within the editor.

> Warning: some symbols (such as file-type and symbol-kind icons that you would see in the picker) are not available in the "default" icon set. They usually require a patched font such as [NerdFonts](https://www.nerdfonts.com/) to be installed and configured in your terminal emulator, and the corresponding icon set to be configured in the editor (for example, using `icons = "nerdfonts"` in your configuration file).

| Key | Description | Default |
| --- | --- | --- |
| `picker` | Whether icons in pickers are enabled. | `true` |
| `bufferline` | Whether icons in the buffer line are enabled. | `true` |
| `statusline` | Whether icons in the status line are enabled. | `true` |
Comment on lines +334 to +338
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd still keep this config though, it makes sense to enable support per UI area


### `[editor.soft-wrap]` Section

Options for soft wrapping lines that exceed the view width:
Expand Down
1 change: 1 addition & 0 deletions book/src/generated/typable-cmd.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
| `:cquit`, `:cq` | Quit with exit code (default 1). Accepts an optional integer exit code (:cq 2). |
| `:cquit!`, `:cq!` | Force quit with exit code (default 1) ignoring unsaved changes. Accepts an optional integer exit code (:cq! 2). |
| `:theme` | Change the editor theme (show current theme if no name specified). |
| `:icons` | Change the editor icon flavor (show current flavor if no name specified). |
| `:clipboard-yank` | Yank main selection into system clipboard. |
| `:clipboard-yank-join` | Yank joined selections into system clipboard. A separator can be provided as first argument. Default value is newline. |
| `:primary-clipboard-yank` | Yank main selection into system primary clipboard. |
Expand Down
140 changes: 140 additions & 0 deletions book/src/icons.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
# Icons

## Requirements

File-type and symbol-kind icons require a patched font such as [NerdFonts](https://www.nerdfonts.com/) to be installed and configured in your terminal emulator. These types of fonts are called *patched* fonts because they define arbitrary symbols for a range of Unicode values, which may vary from one font to another. Therefore, you need to use an icon flavor adapted to your configured terminal font, otherwise you may end up with undefined characters and mismatched icons.

To enable file-type and symbol-kind icons within the editor, see the `[editor.icons]` section of the [configuration file](./configuration.md).

To use an icon flavor add `icons = "<name>"` to your [`config.toml`](./configuration.md) at the very top of the file before the first section or select it during runtime using `:icons <name>`.
lazytanuki marked this conversation as resolved.
Show resolved Hide resolved

## Creating an icon flavor

Create a file with the name of your icon flavor as file name (i.e `myicons.toml`) and place it in your `icons` directory (i.e `~/.config/helix/icons`). The directory might have to be created beforehand.

The name "default" is reserved for the builtin icons and cannot be overridden by user defined icons.

The name of the icon flavor must be set using the `name` key.

The default icons.toml can be found [here](https://github.com/helix-editor/helix/blob/master/icons.toml), and user submitted icon flavors [here](https://github.com/helix-editor/helix/blob/master/runtime/icons).

Icons flavors have five sections:

- Diagnostics
- Breakpoints
- Diff
- Symbol kinds
- Mime types

Each line in these sections is specified as below:

```toml
key = { icon = "…", color = "#ff0000" }
```

where `key` represents what you want to style, `icon` specifies the character to show as the icon, and `color` specifies the foreground color of the icon. `color` can be omitted to defer to the defaults.

### Diagnostic icons

The `[diagnostic]` section defines four **required** diagnostic icons:

- `error`
- `warning`
- `info`
- `hint`

These icons appear in the gutter, in the diagnostic pickers as well as in the status line diagnostic component.
By default, they have the foreground color defined in the current theme's corresponding keys.

> An icon flavor TOML file must define all of these icons.

### Diff icons

The `[diff]` section defines three **required** diffing icons:

- `added`
- `deleted`
- `modified`

These icons appear in the gutter.
By default, they have the foreground color defined in the current theme's corresponding keys.

> An icon flavor TOML file must define all of these icons.

### Breakpoint icons

The `[breakpoint]` section defines two **required** breakpoint icons:

- `verified`
- `unverified`

These icons appear in the gutter while using the Debug Adapter Protocol (DAP). Their color depends on the breakpoint's condition and log message, it cannot be overridden by the `color` key.

> An icon flavor TOML file must define all of these icons.

### Symbol kinds icons

The `[symbol-kind]` section defines **optional** icons for the following required LSP-defined symbol kinds:

- `file` (this icon is also used on files for which the mime type has not been defined in the next section, as a "generic file" icon)
- `module`
- `namespace`
- `package`
- `class`
- `method`
- `property`
- `field`
- `constructor`
- `enumeration`
- `interface`
- `variable`
- `function`
- `constant`
- `string`
- `number`
- `boolean`
- `array`
- `object`
- `key`
- `null`
- `enum-member`
- `structure`
- `event`
- `operator`
- `type-parameter`

By default, these icons have the same style as the loaded theme's `keyword` key. Their style can be customized using the `symbolkind` key in the theme configuration file, or it can individually be overridden by their `color` key.

> An icon flavor TOML file must define either none or all of these icons.

### Mime types icons

The `[mime-type]` section defines **optional** icons for mime types or filename, such as:

```toml
[mime-type]
".bashrc" = { icon = "…", color = "#…" }
"LICENSE" = { icon = "…", color = "#…" }
"rs" = { icon = "…", color = "#…" }
```

These icons appear in the file picker, in the statusline `file-type-icon` component, and in the bufferline (when enabled).

> An icon flavor TOML file can define none, some or all of these icons.

### Inheritance

Extend upon other icon flavors by setting the `inherits` property to an existing theme.

```toml
inherits = "nerdfonts"
name = "custom_nerdfonts"

# Override the icon for generic files:
[symbol-kind]
file = {icon = "…"}

# Override the icon for Rust files
[mime-type]
"rs" = { icon = "…", color = "#…" }
```
9 changes: 5 additions & 4 deletions book/src/themes.md
Original file line number Diff line number Diff line change
Expand Up @@ -316,14 +316,15 @@ These scopes are used for theming the editor interface:
| `ui.cursorline.secondary` | The lines of any other cursors ([if cursorline is enabled][editor-section]) |
| `ui.cursorcolumn.primary` | The column of the primary cursor ([if cursorcolumn is enabled][editor-section]) |
| `ui.cursorcolumn.secondary` | The columns of any other cursors ([if cursorcolumn is enabled][editor-section]) |
| `warning` | Diagnostics warning (gutter) |
| `error` | Diagnostics error (gutter) |
| `info` | Diagnostics info (gutter) |
| `hint` | Diagnostics hint (gutter) |
| `warning` | Diagnostics warning icon (gutter, statusline, and diagnostic pickers) |
| `error` | Diagnostics error icon (gutter, statusline, and diagnostic pickers) |
| `info` | Diagnostics info icon (gutter, statusline, and diagnostic pickers) |
| `hint` | Diagnostics hint icon (gutter, statusline, and diagnostic pickers) |
| `diagnostic` | Diagnostics fallback style (editing area) |
| `diagnostic.hint` | Diagnostics hint (editing area) |
| `diagnostic.info` | Diagnostics info (editing area) |
| `diagnostic.warning` | Diagnostics warning (editing area) |
| `diagnostic.error` | Diagnostics error (editing area) |
| `symbolkind` | Symbol kind icons (symbol picker) |

[editor-section]: ./configuration.md#editor-section
119 changes: 116 additions & 3 deletions helix-loader/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
pub mod config;
pub mod grammar;

use anyhow::{anyhow, Result};
use etcetera::base_strategy::{choose_base_strategy, BaseStrategy};
use std::path::{Path, PathBuf};
use once_cell::sync::Lazy;
use std::{
collections::{HashMap, HashSet},
path::{Path, PathBuf},
};
use toml::Value;

pub const VERSION_AND_GIT_HASH: &str = env!("VERSION_AND_GIT_HASH");

Expand Down Expand Up @@ -154,8 +160,6 @@ pub fn log_file() -> PathBuf {
/// where one usually wants to override or add to the array instead of
/// replacing it altogether.
pub fn merge_toml_values(left: toml::Value, right: toml::Value, merge_depth: usize) -> toml::Value {
use toml::Value;

fn get_name(v: &Value) -> Option<&str> {
v.get("name").and_then(Value::as_str)
}
Expand Down Expand Up @@ -227,6 +231,115 @@ pub fn find_workspace() -> (PathBuf, bool) {
(current_dir, true)
}

/// Recursively load a TOML document, merging with any inherited parent files.
///
/// The paths that have been visited in the inheritance hierarchy are tracked
/// to detect and avoid cycling.
///
/// It is possible for one file to inherit from another file with the same name
/// so long as the second file is in a search directory with lower priority.
/// However, it is not recommended that users do this as it will make tracing
/// errors more difficult.
pub fn load_inheritable_toml(
name: &str,
search_directories: &[PathBuf],
visited_paths: &mut HashSet<PathBuf>,
default_toml_data: &HashMap<&str, &Lazy<Value>>,
merge_toml_docs: fn(Value, Value) -> Value,
) -> Result<Value> {
let path = get_toml_path(name, search_directories, visited_paths)?;

let toml_doc = load_toml(&path)?;

let inherits = toml_doc.get("inherits");

let toml_doc = if let Some(parent_toml_name) = inherits {
let parent_toml_name = parent_toml_name.as_str().ok_or_else(|| {
anyhow!(
"{:?}: expected 'inherits' to be a string: {}",
path,
parent_toml_name
)
})?;

let parent_toml_doc = match default_toml_data.get(parent_toml_name) {
Some(p) => (**p).clone(),
None => load_inheritable_toml(
parent_toml_name,
search_directories,
visited_paths,
default_toml_data,
merge_toml_docs,
)?,
};

merge_toml_docs(parent_toml_doc, toml_doc)
} else {
toml_doc
};

Ok(toml_doc)
}

/// Returns the path to the TOML document with the given name
///
/// Ignores paths already visited and follows directory priority order.
fn get_toml_path(
name: &str,
search_directories: &[PathBuf],
visited_paths: &mut HashSet<PathBuf>,
) -> Result<PathBuf> {
let filename = format!("{}.toml", name);

let mut cycle_found = false; // track if there was a path, but it was in a cycle
search_directories
.iter()
.find_map(|dir| {
let path = dir.join(&filename);
if !path.exists() {
None
} else if visited_paths.contains(&path) {
// Avoiding cycle, continuing to look in lower priority directories
cycle_found = true;
None
} else {
visited_paths.insert(path.clone());
Some(path)
}
})
.ok_or_else(|| {
if cycle_found {
anyhow!("Toml: cycle found in inheriting: {}", name)
} else {
anyhow!("Toml: file not found for: {}", name)
}
})
}

// Loads the TOML data as `toml::Value`
fn load_toml(path: &Path) -> Result<Value> {
let data = std::fs::read_to_string(path)?;
let value = toml::from_str(&data)?;

Ok(value)
}

/// Returns the names of the TOML documents within a directory
pub fn read_toml_names(path: &Path) -> Vec<String> {
std::fs::read_dir(path)
.map(|entries| {
entries
.filter_map(|entry| {
let entry = entry.ok()?;
let path = entry.path();
(path.extension()? == "toml")
.then(|| path.file_stem().unwrap().to_string_lossy().into_owned())
})
.collect()
})
.unwrap_or_default()
}

#[cfg(test)]
mod merge_toml_tests {
use std::str;
Expand Down
Loading