-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
feat: Show filepath context in bufferline #13565
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,13 +25,15 @@ use helix_core::{ | |
use helix_view::{ | ||
annotations::diagnostics::DiagnosticFilter, | ||
document::{Mode, SCRATCH_BUFFER_NAME}, | ||
editor::{CompleteAction, CursorShapeConfig}, | ||
editor::{BufferLineContextMode, CompleteAction, CursorShapeConfig}, | ||
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}; | ||
|
||
|
@@ -559,8 +561,12 @@ 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 | ||
pub fn render_bufferline( | ||
editor: &Editor, | ||
viewport: Rect, | ||
surface: &mut Surface, | ||
context: &BufferLineContextMode, | ||
) { | ||
surface.clear_with( | ||
viewport, | ||
editor | ||
|
@@ -582,14 +588,27 @@ impl EditorView { | |
let mut x = viewport.x; | ||
let current_doc = view!(editor).doc; | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For example, here you can add |
||
let fnames = match context { | ||
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() | ||
.to_owned(), | ||
) | ||
})) | ||
} | ||
BufferLineContextMode::Minimal => expand_fname_contexts(editor, SCRATCH_BUFFER_NAME), | ||
}; | ||
|
||
for doc in editor.documents() { | ||
let fname = doc | ||
.path() | ||
.unwrap_or(&scratch) | ||
.file_name() | ||
.unwrap_or_default() | ||
.to_str() | ||
.unwrap_or_default(); | ||
let fname = fnames.get(&doc.id()).unwrap(); | ||
|
||
let style = if current_doc == doc.id() { | ||
bufferline_active | ||
|
@@ -1494,10 +1513,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 if cx.editor.documents.len() > 1 => true, | ||
_ => false, | ||
}; | ||
|
||
|
@@ -1511,7 +1530,12 @@ impl Component for EditorView { | |
cx.editor.resize(editor_area); | ||
|
||
if use_bufferline { | ||
Self::render_bufferline(cx.editor, area.with_height(1), surface); | ||
Self::render_bufferline( | ||
cx.editor, | ||
area.with_height(1), | ||
surface, | ||
&config.bufferline.context, | ||
); | ||
} | ||
|
||
for (view, is_focused) in cx.editor.tree.views() { | ||
|
@@ -1615,3 +1639,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 | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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, | ||
|
@@ -671,10 +710,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, | ||
|
@@ -684,6 +740,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 { | ||
/// Don't expand filenames to be unique | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: I would rephrase this from what it doesn't do to what it does. For example, "Only show filename" or something along those lines |
||
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 { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't need to add an argument to the function since the context can be accessed inside the function via the editor.