Skip to content

Commit e18f52a

Browse files
committed
Homegrown ANSI parser
Fix padding for columnar menu Highlight substring matches too by default Simplify (?) columnar menu
1 parent 4ab7ea0 commit e18f52a

File tree

5 files changed

+180
-141
lines changed

5 files changed

+180
-141
lines changed

Cargo.lock

Lines changed: 0 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ crossbeam = { version = "0.8.2", optional = true }
2424
crossterm = { version = "0.28.1", features = ["serde"] }
2525
fd-lock = "4.0.2"
2626
itertools = "0.13.0"
27-
lazy_static = "1.5.0"
2827
nu-ansi-term = "0.50.0"
2928
rusqlite = { version = "0.37.0", optional = true }
3029
serde = { version = "1.0", features = ["derive"] }

src/menu/columnar_menu.rs

Lines changed: 54 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
1+
use std::borrow::Cow;
2+
13
use super::{Menu, MenuBuilder, MenuEvent, MenuSettings};
24
use crate::{
35
core_editor::Editor,
46
menu_functions::{
5-
can_partially_complete, completer_input, replace_in_buffer, style_suggestion,
7+
can_partially_complete, completer_input, floor_char_boundary, replace_in_buffer,
8+
style_suggestion,
69
},
710
painting::Painter,
811
Completer, Suggestion,
912
};
1013
use nu_ansi_term::ansi::RESET;
14+
use unicode_segmentation::UnicodeSegmentation;
1115
use unicode_width::UnicodeWidthStr;
1216

1317
/// The traversal direction of the menu
@@ -377,98 +381,70 @@ impl ColumnarMenu {
377381
&self,
378382
suggestion: &Suggestion,
379383
index: usize,
380-
empty_space: usize,
381384
use_ansi_coloring: bool,
382385
) -> String {
386+
let selected = index == self.index();
387+
let empty_space = self.get_width().saturating_sub(suggestion.value.width());
388+
383389
if use_ansi_coloring {
384-
// strip quotes
390+
// TODO(ysthakur): let the user strip quotes, rather than doing it here
385391
let is_quote = |c: char| "`'\"".contains(c);
386392
let shortest_base = &self.working_details.shortest_base_string;
387393
let shortest_base = shortest_base
388394
.strip_prefix(is_quote)
389395
.unwrap_or(shortest_base);
390-
let match_len = shortest_base.chars().count();
391396

392-
let suggestion_style = suggestion.style.unwrap_or(self.settings.color.text_style);
397+
let match_indices = if let Some(match_indices) = &suggestion.match_indices {
398+
Cow::Borrowed(match_indices)
399+
} else if let Some(match_pos) = suggestion
400+
.value
401+
.to_lowercase()
402+
.find(&shortest_base.to_lowercase())
403+
{
404+
// Highlight matched substring if one is found
405+
let match_len = shortest_base.graphemes(true).count();
406+
Cow::Owned((match_pos..match_pos + match_len).collect())
407+
} else {
408+
// Don't highlight anything if no match
409+
Cow::Owned(vec![])
410+
};
393411

394412
let left_text_size = self.longest_suggestion + self.default_details.col_padding;
395-
let right_text_size = self.get_width().saturating_sub(left_text_size);
396-
397-
let default_indices = (0..match_len).collect();
398-
let match_indices = suggestion
399-
.match_indices
400-
.as_ref()
401-
.unwrap_or(&default_indices);
413+
let description_size = self.get_width().saturating_sub(left_text_size);
414+
let padding = left_text_size.saturating_sub(suggestion.value.len());
402415

403-
if index == self.index() {
404-
if let Some(description) = &suggestion.description {
416+
let value_style = if selected {
417+
&self.settings.color.selected_text_style
418+
} else {
419+
&suggestion.style.unwrap_or(self.settings.color.text_style)
420+
};
421+
let styled_value = style_suggestion(
422+
value_style.paint(&suggestion.value).as_str(),
423+
match_indices.as_ref(),
424+
&self.settings.color.match_style,
425+
);
426+
427+
if let Some(description) = &suggestion.description {
428+
let styled_desc = self.settings.color.description_style.paint(
429+
description
430+
.chars()
431+
.take(description_size)
432+
.collect::<String>()
433+
.replace('\n', " "),
434+
);
435+
if selected {
405436
format!(
406-
"{:left_text_size$}{}{}{}{}{}",
407-
style_suggestion(
408-
&self
409-
.settings
410-
.color
411-
.selected_text_style
412-
.paint(&suggestion.value)
413-
.to_string(),
414-
match_indices,
415-
&self.settings.color.selected_match_style,
416-
),
417-
self.settings.color.description_style.prefix(),
437+
"{}{}{}{}",
438+
styled_value,
439+
" ".repeat(padding),
418440
self.settings.color.selected_text_style.prefix(),
419-
description
420-
.chars()
421-
.take(right_text_size)
422-
.collect::<String>()
423-
.replace('\n', " "),
424-
RESET,
441+
styled_desc,
425442
)
426443
} else {
427-
format!(
428-
"{}{:>empty$}{}",
429-
style_suggestion(
430-
&self
431-
.settings
432-
.color
433-
.selected_text_style
434-
.paint(&suggestion.value)
435-
.to_string(),
436-
match_indices,
437-
&self.settings.color.selected_match_style,
438-
),
439-
"",
440-
empty = empty_space,
441-
)
444+
format!("{}{}{}", styled_value, " ".repeat(padding), styled_desc)
442445
}
443-
} else if let Some(description) = &suggestion.description {
444-
format!(
445-
"{:left_text_size$}{}{}{}{}",
446-
style_suggestion(
447-
&suggestion_style.paint(&suggestion.value).to_string(),
448-
match_indices,
449-
&self.settings.color.match_style,
450-
),
451-
self.settings.color.description_style.prefix(),
452-
description
453-
.chars()
454-
.take(right_text_size)
455-
.collect::<String>()
456-
.replace('\n', " "),
457-
RESET,
458-
)
459446
} else {
460-
format!(
461-
"{}{}{:>empty$}{}{}",
462-
style_suggestion(
463-
&suggestion_style.paint(&suggestion.value).to_string(),
464-
match_indices,
465-
&self.settings.color.match_style,
466-
),
467-
self.settings.color.description_style.prefix(),
468-
"",
469-
RESET,
470-
empty = empty_space,
471-
)
447+
format!("{}{:>empty$}", styled_value, "", empty = empty_space,)
472448
}
473449
} else {
474450
// If no ansi coloring is found, then the selection word is the line in uppercase
@@ -500,7 +476,7 @@ impl ColumnarMenu {
500476
)
501477
};
502478

503-
if index == self.index() {
479+
if selected {
504480
line.to_uppercase()
505481
} else {
506482
line
@@ -751,14 +727,7 @@ impl Menu for ColumnarMenu {
751727
.step_by(num_rows)
752728
.take(self.get_cols().into())
753729
.map(|(index, suggestion)| {
754-
let empty_space =
755-
self.get_width().saturating_sub(suggestion.value.width());
756-
self.create_string(
757-
suggestion,
758-
index,
759-
empty_space,
760-
use_ansi_coloring,
761-
)
730+
self.create_string(suggestion, index, use_ansi_coloring)
762731
})
763732
.collect();
764733
menu_string.push_str(&row_string);
@@ -779,8 +748,6 @@ impl Menu for ColumnarMenu {
779748
// Correcting the enumerate index based on the number of skipped values
780749
let index = index + skip_values;
781750
let column = index % self.get_cols() as usize;
782-
let empty_space =
783-
self.get_width().saturating_sub(suggestion.value.width());
784751

785752
let end_of_line =
786753
if column == self.get_cols().saturating_sub(1) as usize {
@@ -790,12 +757,7 @@ impl Menu for ColumnarMenu {
790757
};
791758
format!(
792759
"{}{}",
793-
self.create_string(
794-
suggestion,
795-
index,
796-
empty_space,
797-
use_ansi_coloring
798-
),
760+
self.create_string(suggestion, index, use_ansi_coloring),
799761
end_of_line
800762
)
801763
})

src/menu/ide_menu.rs

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ use super::{Menu, MenuBuilder, MenuEvent, MenuSettings};
22
use crate::{
33
core_editor::Editor,
44
menu_functions::{
5-
can_partially_complete, completer_input, replace_in_buffer, style_suggestion,
5+
can_partially_complete, completer_input, floor_char_boundary, replace_in_buffer,
6+
style_suggestion,
67
},
78
painting::Painter,
89
Completer, Suggestion,
@@ -518,19 +519,45 @@ impl IdeMenu {
518519
};
519520

520521
if use_ansi_coloring {
521-
// strip quotes
522+
// TODO(ysthakur): let the user strip quotes, rather than doing it here
522523
let is_quote = |c: char| "`'\"".contains(c);
523524
let shortest_base = &self.working_details.shortest_base_string;
524525
let shortest_base = shortest_base
525526
.strip_prefix(is_quote)
526527
.unwrap_or(shortest_base);
527-
let match_len = shortest_base.chars().count().min(string.chars().count());
528528

529-
let default_indices = (0..match_len).collect();
529+
// Highlight the first match of the shortest base string by default
530+
let match_len = shortest_base
531+
.graphemes(true)
532+
.count()
533+
.min(string.graphemes(true).count());
534+
let default_indices = string
535+
.to_lowercase()
536+
.find(shortest_base)
537+
.map(|match_pos| (match_pos..match_pos + match_len).collect())
538+
.unwrap_or_default();
530539
let match_indices = suggestion
531540
.match_indices
532541
.as_ref()
533542
.unwrap_or(&default_indices);
543+
let match_len = shortest_base.chars().count().min(string.chars().count());
544+
545+
// Find match position - look for the base string in the suggestion (case-insensitive)
546+
let match_position = string
547+
.to_lowercase()
548+
.find(&shortest_base.to_lowercase())
549+
.unwrap_or(0);
550+
551+
// The match is just the part that matches the shortest_base
552+
let match_str = {
553+
let match_str = &string[match_position..];
554+
let match_len_bytes = match_str
555+
.char_indices()
556+
.nth(match_len)
557+
.map(|(i, _)| i)
558+
.unwrap_or_else(|| match_str.len());
559+
&string[match_position..match_position + match_len_bytes]
560+
};
534561

535562
let suggestion_style = suggestion.style.unwrap_or(self.settings.color.text_style);
536563

@@ -545,7 +572,7 @@ impl IdeMenu {
545572
.settings
546573
.color
547574
.selected_text_style
548-
.paint(&suggestion.value)
575+
.paint(&string)
549576
.to_string(),
550577
match_indices,
551578
&self.settings.color.selected_match_style,
@@ -561,7 +588,7 @@ impl IdeMenu {
561588
suggestion_style.prefix(),
562589
" ".repeat(padding),
563590
style_suggestion(
564-
&suggestion_style.paint(&suggestion.value).to_string(),
591+
&suggestion_style.paint(&string).to_string(),
565592
match_indices,
566593
&self.settings.color.match_style,
567594
),

0 commit comments

Comments
 (0)