@@ -25,13 +25,15 @@ use helix_core::{
25
25
use helix_view:: {
26
26
annotations:: diagnostics:: DiagnosticFilter ,
27
27
document:: { Mode , SCRATCH_BUFFER_NAME } ,
28
- editor:: { CompleteAction , CursorShapeConfig } ,
28
+ editor:: { BufferLineContextMode , CompleteAction , CursorShapeConfig } ,
29
29
graphics:: { Color , CursorKind , Modifier , Rect , Style } ,
30
30
input:: { KeyEvent , MouseButton , MouseEvent , MouseEventKind } ,
31
31
keyboard:: { KeyCode , KeyModifiers } ,
32
- Document , Editor , Theme , View ,
32
+ Document , DocumentId , Editor , Theme , View ,
33
+ } ;
34
+ use std:: {
35
+ collections:: HashMap , ffi:: OsString , mem:: take, num:: NonZeroUsize , ops, path:: PathBuf , rc:: Rc ,
33
36
} ;
34
- use std:: { mem:: take, num:: NonZeroUsize , ops, path:: PathBuf , rc:: Rc } ;
35
37
36
38
use tui:: { buffer:: Buffer as Surface , text:: Span } ;
37
39
@@ -559,8 +561,12 @@ impl EditorView {
559
561
}
560
562
561
563
/// Render bufferline at the top
562
- pub fn render_bufferline ( editor : & Editor , viewport : Rect , surface : & mut Surface ) {
563
- let scratch = PathBuf :: from ( SCRATCH_BUFFER_NAME ) ; // default filename to use for scratch buffer
564
+ pub fn render_bufferline (
565
+ editor : & Editor ,
566
+ viewport : Rect ,
567
+ surface : & mut Surface ,
568
+ context : & BufferLineContextMode ,
569
+ ) {
564
570
surface. clear_with (
565
571
viewport,
566
572
editor
@@ -582,14 +588,27 @@ impl EditorView {
582
588
let mut x = viewport. x ;
583
589
let current_doc = view ! ( editor) . doc ;
584
590
591
+ let fnames = match context {
592
+ BufferLineContextMode :: None => {
593
+ let scratch = PathBuf :: from ( SCRATCH_BUFFER_NAME ) ; // default filename to use for scratch buffer
594
+ HashMap :: < DocumentId , String > :: from_iter ( editor. documents ( ) . map ( |doc| {
595
+ (
596
+ doc. id ( ) ,
597
+ doc. path ( )
598
+ . unwrap_or ( & scratch)
599
+ . file_name ( )
600
+ . unwrap_or_default ( )
601
+ . to_str ( )
602
+ . unwrap_or_default ( )
603
+ . to_owned ( ) ,
604
+ )
605
+ } ) )
606
+ }
607
+ BufferLineContextMode :: Minimal => expand_fname_contexts ( editor, SCRATCH_BUFFER_NAME ) ,
608
+ } ;
609
+
585
610
for doc in editor. documents ( ) {
586
- let fname = doc
587
- . path ( )
588
- . unwrap_or ( & scratch)
589
- . file_name ( )
590
- . unwrap_or_default ( )
591
- . to_str ( )
592
- . unwrap_or_default ( ) ;
611
+ let fname = fnames. get ( & doc. id ( ) ) . unwrap ( ) ;
593
612
594
613
let style = if current_doc == doc. id ( ) {
595
614
bufferline_active
@@ -1494,10 +1513,10 @@ impl Component for EditorView {
1494
1513
let config = cx. editor . config ( ) ;
1495
1514
1496
1515
// check if bufferline should be rendered
1497
- use helix_view:: editor:: BufferLine ;
1498
- let use_bufferline = match config. bufferline {
1499
- BufferLine :: Always => true ,
1500
- BufferLine :: Multiple if cx. editor . documents . len ( ) > 1 => true ,
1516
+ use helix_view:: editor:: BufferLineRenderMode ;
1517
+ let use_bufferline = match config. bufferline . show {
1518
+ BufferLineRenderMode :: Always => true ,
1519
+ BufferLineRenderMode :: Multiple if cx. editor . documents . len ( ) > 1 => true ,
1501
1520
_ => false ,
1502
1521
} ;
1503
1522
@@ -1511,7 +1530,12 @@ impl Component for EditorView {
1511
1530
cx. editor . resize ( editor_area) ;
1512
1531
1513
1532
if use_bufferline {
1514
- Self :: render_bufferline ( cx. editor , area. with_height ( 1 ) , surface) ;
1533
+ Self :: render_bufferline (
1534
+ cx. editor ,
1535
+ area. with_height ( 1 ) ,
1536
+ surface,
1537
+ & config. bufferline . context ,
1538
+ ) ;
1515
1539
}
1516
1540
1517
1541
for ( view, is_focused) in cx. editor . tree . views ( ) {
@@ -1615,3 +1639,72 @@ fn canonicalize_key(key: &mut KeyEvent) {
1615
1639
key. modifiers . remove ( KeyModifiers :: SHIFT )
1616
1640
}
1617
1641
}
1642
+
1643
+ #[ derive( Default ) ]
1644
+ struct PathTrie {
1645
+ parents : HashMap < OsString , PathTrie > ,
1646
+ visits : u32 ,
1647
+ }
1648
+
1649
+ /// Returns a unique path ending for the current set of documents in the
1650
+ /// editor. For example, documents `a/b` and `c/d` would resolve to `b` and `d`
1651
+ /// respectively, while `a/b/c` and `a/d/c` would resolve to `b/c` and `d/c`
1652
+ /// respectively.
1653
+ fn expand_fname_contexts < ' a > ( editor : & ' a Editor , scratch : & ' a str ) -> HashMap < DocumentId , String > {
1654
+ let mut trie = HashMap :: new ( ) ;
1655
+
1656
+ // Build out a reverse prefix trie for all documents
1657
+ for doc in editor. documents ( ) {
1658
+ let Some ( path) = doc. path ( ) else {
1659
+ continue ;
1660
+ } ;
1661
+
1662
+ let mut current_subtrie = & mut trie;
1663
+
1664
+ for component in path. components ( ) . rev ( ) {
1665
+ let segment = component. as_os_str ( ) . to_os_string ( ) ;
1666
+ let subtrie = current_subtrie
1667
+ . entry ( segment)
1668
+ . or_insert_with ( PathTrie :: default) ;
1669
+
1670
+ subtrie. visits += 1 ;
1671
+ current_subtrie = & mut subtrie. parents ;
1672
+ }
1673
+ }
1674
+
1675
+ let mut fnames = HashMap :: new ( ) ;
1676
+
1677
+ // Navigate the built reverse prefix trie to find the smallest unique path
1678
+ for doc in editor. documents ( ) {
1679
+ let Some ( path) = doc. path ( ) else {
1680
+ fnames. insert ( doc. id ( ) , scratch. to_owned ( ) ) ;
1681
+ continue ;
1682
+ } ;
1683
+
1684
+ let mut current_subtrie = & trie;
1685
+ let mut built_path = vec ! [ ] ;
1686
+
1687
+ for component in path. components ( ) . rev ( ) {
1688
+ let segment = component. as_os_str ( ) . to_os_string ( ) ;
1689
+ let subtrie = current_subtrie
1690
+ . get ( & segment)
1691
+ . expect ( "should have contained segment" ) ;
1692
+
1693
+ built_path. insert ( 0 , segment) ;
1694
+
1695
+ if subtrie. visits == 1 {
1696
+ fnames. insert (
1697
+ doc. id ( ) ,
1698
+ PathBuf :: from_iter ( built_path. iter ( ) )
1699
+ . to_string_lossy ( )
1700
+ . into_owned ( ) ,
1701
+ ) ;
1702
+ break ;
1703
+ }
1704
+
1705
+ current_subtrie = & subtrie. parents ;
1706
+ }
1707
+ }
1708
+
1709
+ fnames
1710
+ }
0 commit comments