mirror of https://github.com/helix-editor/helix
Merge bc0f718df5
into 205e7ece70
commit
83f43d25ba
|
@ -3,6 +3,7 @@
|
|||
- [`[editor]` Section](#editor-section)
|
||||
- [`[editor.clipboard-provider]` Section](#editorclipboard-provider-section)
|
||||
- [`[editor.statusline]` Section](#editorstatusline-section)
|
||||
- [`[editor.bufferline]` Section](#editorbufferline-section)
|
||||
- [`[editor.lsp]` Section](#editorlsp-section)
|
||||
- [`[editor.cursor-shape]` Section](#editorcursor-shape-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 |
|
||||
| `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
|
||||
|
||||
| Key | Description | Default |
|
||||
|
|
|
@ -29,9 +29,11 @@ use helix_view::{
|
|||
graphics::{Color, CursorKind, Modifier, Rect, Style},
|
||||
input::{KeyEvent, MouseButton, MouseEvent, MouseEventKind},
|
||||
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};
|
||||
|
||||
|
@ -560,7 +562,6 @@ impl EditorView {
|
|||
|
||||
/// Render bufferline at the top
|
||||
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(
|
||||
viewport,
|
||||
editor
|
||||
|
@ -582,14 +583,28 @@ impl EditorView {
|
|||
let mut x = viewport.x;
|
||||
let current_doc = view!(editor).doc;
|
||||
|
||||
for doc in editor.documents() {
|
||||
let fname = doc
|
||||
.path()
|
||||
use helix_view::editor::BufferLineContextMode;
|
||||
let fnames = match editor.config().bufferline.context.clone() {
|
||||
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)
|
||||
.file_name()
|
||||
.unwrap_or_default()
|
||||
.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() {
|
||||
bufferline_active
|
||||
|
@ -1494,10 +1509,10 @@ impl Component for EditorView {
|
|||
let config = cx.editor.config();
|
||||
|
||||
// check if bufferline should be rendered
|
||||
use helix_view::editor::BufferLine;
|
||||
let use_bufferline = match config.bufferline {
|
||||
BufferLine::Always => true,
|
||||
BufferLine::Multiple if cx.editor.documents.len() > 1 => true,
|
||||
use helix_view::editor::BufferLineRenderMode;
|
||||
let use_bufferline = match config.bufferline.show {
|
||||
BufferLineRenderMode::Always => true,
|
||||
BufferLineRenderMode::Multiple => 1 < cx.editor.documents.len(),
|
||||
_ => false,
|
||||
};
|
||||
|
||||
|
@ -1615,3 +1630,72 @@ fn canonicalize_key(key: &mut KeyEvent) {
|
|||
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_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::{
|
||||
access::{DynAccess, DynGuard},
|
||||
|
@ -162,6 +166,40 @@ where
|
|||
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)]
|
||||
#[serde(rename_all = "kebab-case", default, deny_unknown_fields)]
|
||||
pub struct GutterLineNumbersConfig {
|
||||
|
@ -334,6 +372,7 @@ pub struct Config {
|
|||
#[serde(default)]
|
||||
pub whitespace: WhitespaceConfig,
|
||||
/// Persistently display open buffers along the top
|
||||
#[serde(deserialize_with = "deserialize_bufferline_show_or_struct")]
|
||||
pub bufferline: BufferLine,
|
||||
/// Vertical indent width guides.
|
||||
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
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum BufferLine {
|
||||
pub enum BufferLineRenderMode {
|
||||
/// Don't render bufferline
|
||||
#[default]
|
||||
Never,
|
||||
|
@ -691,6 +747,18 @@ pub enum BufferLine {
|
|||
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)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum LineNumber {
|
||||
|
|
Loading…
Reference in New Issue