mirror of https://github.com/helix-editor/helix
Merge bc0f718df5
into 205e7ece70
commit
83f43d25ba
|
@ -3,6 +3,7 @@
|
||||||
- [`[editor]` Section](#editor-section)
|
- [`[editor]` Section](#editor-section)
|
||||||
- [`[editor.clipboard-provider]` Section](#editorclipboard-provider-section)
|
- [`[editor.clipboard-provider]` Section](#editorclipboard-provider-section)
|
||||||
- [`[editor.statusline]` Section](#editorstatusline-section)
|
- [`[editor.statusline]` Section](#editorstatusline-section)
|
||||||
|
- [`[editor.bufferline]` Section](#editorbufferline-section)
|
||||||
- [`[editor.lsp]` Section](#editorlsp-section)
|
- [`[editor.lsp]` Section](#editorlsp-section)
|
||||||
- [`[editor.cursor-shape]` Section](#editorcursor-shape-section)
|
- [`[editor.cursor-shape]` Section](#editorcursor-shape-section)
|
||||||
- [`[editor.file-picker]` Section](#editorfile-picker-section)
|
- [`[editor.file-picker]` Section](#editorfile-picker-section)
|
||||||
|
@ -148,6 +149,32 @@ The following statusline elements can be configured:
|
||||||
| `version-control` | The current branch name or detached commit hash of the opened workspace |
|
| `version-control` | The current branch name or detached commit hash of the opened workspace |
|
||||||
| `register` | The current selected register |
|
| `register` | The current selected register |
|
||||||
|
|
||||||
|
### `[editor.bufferline]` Section
|
||||||
|
|
||||||
|
For simplicity, `editor.bufferline` accepts a render mode, which will use
|
||||||
|
default settings for the rest of the configuration.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[editor]
|
||||||
|
bufferline = "always"
|
||||||
|
```
|
||||||
|
|
||||||
|
To customize the behavior of the bufferline, the `[editor.bufferline]` section
|
||||||
|
must be used.
|
||||||
|
|
||||||
|
| Key | Description | Default |
|
||||||
|
| --------- | ------------------------------------------------------------------------------------------------------------------- | ----------- |
|
||||||
|
| `show` | When to show the bufferline. Can be `always`, `never` or `multiple` (only shown if more than one buffer is in use). | `"never"` |
|
||||||
|
| `context` | Whether to provide additional filepath context. Can be `minimal` or `none`. | `"minimal"` |
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[editor.bufferline]
|
||||||
|
show = "always"
|
||||||
|
context = "none"
|
||||||
|
```
|
||||||
|
|
||||||
### `[editor.lsp]` Section
|
### `[editor.lsp]` Section
|
||||||
|
|
||||||
| Key | Description | Default |
|
| Key | Description | Default |
|
||||||
|
|
|
@ -29,9 +29,11 @@ use helix_view::{
|
||||||
graphics::{Color, CursorKind, Modifier, Rect, Style},
|
graphics::{Color, CursorKind, Modifier, Rect, Style},
|
||||||
input::{KeyEvent, MouseButton, MouseEvent, MouseEventKind},
|
input::{KeyEvent, MouseButton, MouseEvent, MouseEventKind},
|
||||||
keyboard::{KeyCode, KeyModifiers},
|
keyboard::{KeyCode, KeyModifiers},
|
||||||
Document, Editor, Theme, View,
|
Document, DocumentId, Editor, Theme, View,
|
||||||
|
};
|
||||||
|
use std::{
|
||||||
|
collections::HashMap, ffi::OsString, mem::take, num::NonZeroUsize, ops, path::PathBuf, rc::Rc,
|
||||||
};
|
};
|
||||||
use std::{mem::take, num::NonZeroUsize, ops, path::PathBuf, rc::Rc};
|
|
||||||
|
|
||||||
use tui::{buffer::Buffer as Surface, text::Span};
|
use tui::{buffer::Buffer as Surface, text::Span};
|
||||||
|
|
||||||
|
@ -560,7 +562,6 @@ impl EditorView {
|
||||||
|
|
||||||
/// Render bufferline at the top
|
/// Render bufferline at the top
|
||||||
pub fn render_bufferline(editor: &Editor, viewport: Rect, surface: &mut Surface) {
|
pub fn render_bufferline(editor: &Editor, viewport: Rect, surface: &mut Surface) {
|
||||||
let scratch = PathBuf::from(SCRATCH_BUFFER_NAME); // default filename to use for scratch buffer
|
|
||||||
surface.clear_with(
|
surface.clear_with(
|
||||||
viewport,
|
viewport,
|
||||||
editor
|
editor
|
||||||
|
@ -582,14 +583,28 @@ 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() {
|
use helix_view::editor::BufferLineContextMode;
|
||||||
let fname = doc
|
let fnames = match editor.config().bufferline.context.clone() {
|
||||||
.path()
|
BufferLineContextMode::None => {
|
||||||
|
let scratch = PathBuf::from(SCRATCH_BUFFER_NAME); // default filename to use for scratch buffer
|
||||||
|
HashMap::<DocumentId, String>::from_iter(editor.documents().map(|doc| {
|
||||||
|
(
|
||||||
|
doc.id(),
|
||||||
|
doc.path()
|
||||||
.unwrap_or(&scratch)
|
.unwrap_or(&scratch)
|
||||||
.file_name()
|
.file_name()
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.to_str()
|
.to_str()
|
||||||
.unwrap_or_default();
|
.unwrap_or_default()
|
||||||
|
.to_owned(),
|
||||||
|
)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
BufferLineContextMode::Minimal => expand_fname_contexts(editor, SCRATCH_BUFFER_NAME),
|
||||||
|
};
|
||||||
|
|
||||||
|
for doc in editor.documents() {
|
||||||
|
let fname = fnames.get(&doc.id()).unwrap();
|
||||||
|
|
||||||
let style = if current_doc == doc.id() {
|
let style = if current_doc == doc.id() {
|
||||||
bufferline_active
|
bufferline_active
|
||||||
|
@ -1494,10 +1509,10 @@ impl Component for EditorView {
|
||||||
let config = cx.editor.config();
|
let config = cx.editor.config();
|
||||||
|
|
||||||
// check if bufferline should be rendered
|
// check if bufferline should be rendered
|
||||||
use helix_view::editor::BufferLine;
|
use helix_view::editor::BufferLineRenderMode;
|
||||||
let use_bufferline = match config.bufferline {
|
let use_bufferline = match config.bufferline.show {
|
||||||
BufferLine::Always => true,
|
BufferLineRenderMode::Always => true,
|
||||||
BufferLine::Multiple if cx.editor.documents.len() > 1 => true,
|
BufferLineRenderMode::Multiple => 1 < cx.editor.documents.len(),
|
||||||
_ => false,
|
_ => false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1615,3 +1630,72 @@ fn canonicalize_key(key: &mut KeyEvent) {
|
||||||
key.modifiers.remove(KeyModifiers::SHIFT)
|
key.modifiers.remove(KeyModifiers::SHIFT)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct PathTrie {
|
||||||
|
parents: HashMap<OsString, PathTrie>,
|
||||||
|
visits: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a unique path ending for the current set of documents in the
|
||||||
|
/// editor. For example, documents `a/b` and `c/d` would resolve to `b` and `d`
|
||||||
|
/// respectively, while `a/b/c` and `a/d/c` would resolve to `b/c` and `d/c`
|
||||||
|
/// respectively.
|
||||||
|
fn expand_fname_contexts<'a>(editor: &'a Editor, scratch: &'a str) -> HashMap<DocumentId, String> {
|
||||||
|
let mut trie = HashMap::new();
|
||||||
|
|
||||||
|
// Build out a reverse prefix trie for all documents
|
||||||
|
for doc in editor.documents() {
|
||||||
|
let Some(path) = doc.path() else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut current_subtrie = &mut trie;
|
||||||
|
|
||||||
|
for component in path.components().rev() {
|
||||||
|
let segment = component.as_os_str().to_os_string();
|
||||||
|
let subtrie = current_subtrie
|
||||||
|
.entry(segment)
|
||||||
|
.or_insert_with(PathTrie::default);
|
||||||
|
|
||||||
|
subtrie.visits += 1;
|
||||||
|
current_subtrie = &mut subtrie.parents;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut fnames = HashMap::new();
|
||||||
|
|
||||||
|
// Navigate the built reverse prefix trie to find the smallest unique path
|
||||||
|
for doc in editor.documents() {
|
||||||
|
let Some(path) = doc.path() else {
|
||||||
|
fnames.insert(doc.id(), scratch.to_owned());
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut current_subtrie = ≜
|
||||||
|
let mut built_path = vec![];
|
||||||
|
|
||||||
|
for component in path.components().rev() {
|
||||||
|
let segment = component.as_os_str().to_os_string();
|
||||||
|
let subtrie = current_subtrie
|
||||||
|
.get(&segment)
|
||||||
|
.expect("should have contained segment");
|
||||||
|
|
||||||
|
built_path.insert(0, segment);
|
||||||
|
|
||||||
|
if subtrie.visits == 1 {
|
||||||
|
fnames.insert(
|
||||||
|
doc.id(),
|
||||||
|
PathBuf::from_iter(built_path.iter())
|
||||||
|
.to_string_lossy()
|
||||||
|
.into_owned(),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
current_subtrie = &subtrie.parents;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fnames
|
||||||
|
}
|
||||||
|
|
|
@ -56,7 +56,11 @@ use helix_dap as dap;
|
||||||
use helix_lsp::lsp;
|
use helix_lsp::lsp;
|
||||||
use helix_stdx::path::canonicalize;
|
use helix_stdx::path::canonicalize;
|
||||||
|
|
||||||
use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer};
|
use serde::{
|
||||||
|
de::{self, IntoDeserializer},
|
||||||
|
ser::SerializeMap,
|
||||||
|
Deserialize, Deserializer, Serialize, Serializer,
|
||||||
|
};
|
||||||
|
|
||||||
use arc_swap::{
|
use arc_swap::{
|
||||||
access::{DynAccess, DynGuard},
|
access::{DynAccess, DynGuard},
|
||||||
|
@ -162,6 +166,40 @@ where
|
||||||
deserializer.deserialize_any(GutterVisitor)
|
deserializer.deserialize_any(GutterVisitor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn deserialize_bufferline_show_or_struct<'de, D>(deserializer: D) -> Result<BufferLine, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
struct BufferLineVisitor;
|
||||||
|
|
||||||
|
impl<'de> serde::de::Visitor<'de> for BufferLineVisitor {
|
||||||
|
type Value = BufferLine;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
formatter,
|
||||||
|
"a bufferline render mode or a detailed bufferline configuration"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||||
|
where
|
||||||
|
E: serde::de::Error,
|
||||||
|
{
|
||||||
|
Ok(BufferLineRenderMode::deserialize(v.into_deserializer())?.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_map<A>(self, map: A) -> Result<Self::Value, A::Error>
|
||||||
|
where
|
||||||
|
A: serde::de::MapAccess<'de>,
|
||||||
|
{
|
||||||
|
BufferLine::deserialize(de::value::MapAccessDeserializer::new(map))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deserializer.deserialize_any(BufferLineVisitor)
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "kebab-case", default, deny_unknown_fields)]
|
#[serde(rename_all = "kebab-case", default, deny_unknown_fields)]
|
||||||
pub struct GutterLineNumbersConfig {
|
pub struct GutterLineNumbersConfig {
|
||||||
|
@ -334,6 +372,7 @@ pub struct Config {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub whitespace: WhitespaceConfig,
|
pub whitespace: WhitespaceConfig,
|
||||||
/// Persistently display open buffers along the top
|
/// Persistently display open buffers along the top
|
||||||
|
#[serde(deserialize_with = "deserialize_bufferline_show_or_struct")]
|
||||||
pub bufferline: BufferLine,
|
pub bufferline: BufferLine,
|
||||||
/// Vertical indent width guides.
|
/// Vertical indent width guides.
|
||||||
pub indent_guides: IndentGuidesConfig,
|
pub indent_guides: IndentGuidesConfig,
|
||||||
|
@ -678,10 +717,27 @@ impl Default for CursorShapeConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Bufferline configuration
|
||||||
|
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
pub struct BufferLine {
|
||||||
|
pub show: BufferLineRenderMode,
|
||||||
|
pub context: BufferLineContextMode,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<BufferLineRenderMode> for BufferLine {
|
||||||
|
fn from(show: BufferLineRenderMode) -> Self {
|
||||||
|
Self {
|
||||||
|
show,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// bufferline render modes
|
/// bufferline render modes
|
||||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
pub enum BufferLine {
|
pub enum BufferLineRenderMode {
|
||||||
/// Don't render bufferline
|
/// Don't render bufferline
|
||||||
#[default]
|
#[default]
|
||||||
Never,
|
Never,
|
||||||
|
@ -691,6 +747,18 @@ pub enum BufferLine {
|
||||||
Multiple,
|
Multiple,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Bufferline filename context modes
|
||||||
|
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
pub enum BufferLineContextMode {
|
||||||
|
/// Only show the filename
|
||||||
|
None,
|
||||||
|
|
||||||
|
/// Expand filenames to the smallest unique path
|
||||||
|
#[default]
|
||||||
|
Minimal,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
pub enum LineNumber {
|
pub enum LineNumber {
|
||||||
|
|
Loading…
Reference in New Issue