Skip to content

Commit

Permalink
type menu::Item::TieBreaker as second ordering rule on equal scores
Browse files Browse the repository at this point in the history
  • Loading branch information
estin committed Nov 17, 2023
1 parent c05a62e commit 56e4302
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 22 deletions.
16 changes: 16 additions & 0 deletions helix-term/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2114,6 +2114,7 @@ fn global_search(cx: &mut Context) {

impl ui::menu::Item for FileResult {
type Data = Option<PathBuf>;
type TieBreaker = ();

fn format(&self, current_path: &Self::Data) -> Row {
let relative_path = helix_core::path::get_relative_path(&self.path)
Expand All @@ -2129,6 +2130,9 @@ fn global_search(cx: &mut Context) {
relative_path.into()
}
}

#[inline]
fn tie_breaker(&self, _data: &Self::Data) -> Self::TieBreaker {}
}

let config = cx.editor.config();
Expand Down Expand Up @@ -2701,6 +2705,7 @@ fn buffer_picker(cx: &mut Context) {

impl ui::menu::Item for BufferMeta {
type Data = ();
type TieBreaker = ();

fn format(&self, _data: &Self::Data) -> Row {
let path = self
Expand All @@ -2722,6 +2727,9 @@ fn buffer_picker(cx: &mut Context) {

Row::new([self.id.to_string(), flags, path.to_string()])
}

#[inline]
fn tie_breaker(&self, _data: &Self::Data) -> Self::TieBreaker {}
}

let new_meta = |doc: &Document| BufferMeta {
Expand Down Expand Up @@ -2768,6 +2776,7 @@ fn jumplist_picker(cx: &mut Context) {

impl ui::menu::Item for JumpMeta {
type Data = ();
type TieBreaker = ();

fn format(&self, _data: &Self::Data) -> Row {
let path = self
Expand All @@ -2791,6 +2800,9 @@ fn jumplist_picker(cx: &mut Context) {
};
format!("{} {}{} {}", self.id, path, flag, self.text).into()
}

#[inline]
fn tie_breaker(&self, _data: &Self::Data) -> Self::TieBreaker {}
}

for (view, _) in cx.editor.tree.views_mut() {
Expand Down Expand Up @@ -2850,6 +2862,7 @@ fn jumplist_picker(cx: &mut Context) {

impl ui::menu::Item for MappableCommand {
type Data = ReverseKeymap;
type TieBreaker = ();

fn format(&self, keymap: &Self::Data) -> Row {
let fmt_binding = |bindings: &Vec<Vec<KeyEvent>>| -> String {
Expand All @@ -2875,6 +2888,9 @@ impl ui::menu::Item for MappableCommand {
},
}
}

#[inline]
fn tie_breaker(&self, _data: &Self::Data) -> Self::TieBreaker {}
}

pub fn command_palette(cx: &mut Context) {
Expand Down
12 changes: 12 additions & 0 deletions helix-term/src/commands/dap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,22 +24,31 @@ use helix_view::handlers::dap::{breakpoints_changed, jump_to_stack_frame, select

impl ui::menu::Item for StackFrame {
type Data = ();
type TieBreaker = ();

fn format(&self, _data: &Self::Data) -> Row {
self.name.as_str().into() // TODO: include thread_states in the label
}

#[inline]
fn tie_breaker(&self, _data: &Self::Data) -> Self::TieBreaker {}
}

impl ui::menu::Item for DebugTemplate {
type Data = ();
type TieBreaker = ();

fn format(&self, _data: &Self::Data) -> Row {
self.name.as_str().into()
}

#[inline]
fn tie_breaker(&self, _data: &Self::Data) -> Self::TieBreaker {}
}

impl ui::menu::Item for Thread {
type Data = ThreadStates;
type TieBreaker = ();

fn format(&self, thread_states: &Self::Data) -> Row {
format!(
Expand All @@ -52,6 +61,9 @@ impl ui::menu::Item for Thread {
)
.into()
}

#[inline]
fn tie_breaker(&self, _data: &Self::Data) -> Self::TieBreaker {}
}

fn thread_picker(
Expand Down
17 changes: 17 additions & 0 deletions helix-term/src/commands/lsp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ macro_rules! language_server_with_feature {
impl ui::menu::Item for lsp::Location {
/// Current working directory.
type Data = PathBuf;
type TieBreaker = ();

fn format(&self, cwdir: &Self::Data) -> Row {
// The preallocation here will overallocate a few characters since it will account for the
Expand Down Expand Up @@ -99,6 +100,9 @@ impl ui::menu::Item for lsp::Location {
.expect("Will only failed if allocating fail");
res.into()
}

#[inline]
fn tie_breaker(&self, _data: &Self::Data) -> Self::TieBreaker {}
}

struct SymbolInformationItem {
Expand All @@ -109,6 +113,7 @@ struct SymbolInformationItem {
impl ui::menu::Item for SymbolInformationItem {
/// Path to currently focussed document
type Data = Option<lsp::Url>;
type TieBreaker = ();

fn format(&self, current_doc_path: &Self::Data) -> Row {
if current_doc_path.as_ref() == Some(&self.symbol.location.uri) {
Expand All @@ -128,6 +133,9 @@ impl ui::menu::Item for SymbolInformationItem {
}
}
}

#[inline]
fn tie_breaker(&self, _data: &Self::Data) -> Self::TieBreaker {}
}

struct DiagnosticStyles {
Expand All @@ -145,6 +153,7 @@ struct PickerDiagnostic {

impl ui::menu::Item for PickerDiagnostic {
type Data = (DiagnosticStyles, DiagnosticsFormat);
type TieBreaker = ();

fn format(&self, (styles, format): &Self::Data) -> Row {
let mut style = self
Expand Down Expand Up @@ -184,6 +193,8 @@ impl ui::menu::Item for PickerDiagnostic {
])
.into()
}
#[inline]
fn tie_breaker(&self, _data: &Self::Data) -> Self::TieBreaker {}
}

fn location_to_file_location(location: &lsp::Location) -> FileLocation {
Expand Down Expand Up @@ -512,12 +523,15 @@ struct CodeActionOrCommandItem {

impl ui::menu::Item for CodeActionOrCommandItem {
type Data = ();
type TieBreaker = ();
fn format(&self, _data: &Self::Data) -> Row {
match &self.lsp_item {
lsp::CodeActionOrCommand::CodeAction(action) => action.title.as_str().into(),
lsp::CodeActionOrCommand::Command(command) => command.title.as_str().into(),
}
}
#[inline]
fn tie_breaker(&self, _data: &Self::Data) -> Self::TieBreaker {}
}

/// Determines the category of the `CodeAction` using the `CodeAction::kind` field.
Expand Down Expand Up @@ -754,9 +768,12 @@ pub fn code_action(cx: &mut Context) {

impl ui::menu::Item for lsp::Command {
type Data = ();
type TieBreaker = ();
fn format(&self, _data: &Self::Data) -> Row {
self.title.as_str().into()
}
#[inline]
fn tie_breaker(&self, _data: &Self::Data) -> Self::TieBreaker {}
}

pub fn execute_lsp_command(editor: &mut Editor, language_server_id: usize, cmd: lsp::Command) {
Expand Down
11 changes: 5 additions & 6 deletions helix-term/src/ui/completion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,10 @@ use crate::ui::{menu, Markdown, Menu, Popup, PromptEvent};

use helix_lsp::{lsp, util, OffsetEncoding};

const WEIGHT_SCORE_STEP: u32 = 1_000_000;
const WEIGHT_MAX_LS_LEN: u32 = 100;

impl menu::Item for CompletionItem {
type Data = ();
type TieBreaker = usize;

fn sort_text(&self, data: &Self::Data) -> Cow<str> {
self.filter_text(data)
}
Expand Down Expand Up @@ -87,8 +86,8 @@ impl menu::Item for CompletionItem {
}

#[inline]
fn weight(&self, _data: &Self::Data) -> u32 {
(WEIGHT_MAX_LS_LEN - self.language_server_id as u32) * WEIGHT_SCORE_STEP
fn tie_breaker(&self, _data: &Self::Data) -> Self::TieBreaker {
self.language_server_id
}
}

Expand All @@ -101,7 +100,7 @@ pub struct CompletionItem {

/// Wraps a Menu.
pub struct Completion {
popup: Popup<Menu<CompletionItem>>,
popup: Popup<Menu<CompletionItem, usize>>,
start_offset: usize,
#[allow(dead_code)]
trigger_offset: usize,
Expand Down
39 changes: 23 additions & 16 deletions helix-term/src/ui/menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ pub trait Item: Sync + Send + 'static {
/// Additional editor state that is used for label calculation.
type Data: Sync + Send + 'static;

/// Second ordering rule on equals items score
type TieBreaker;

fn format(&self, data: &Self::Data) -> Row;

fn sort_text(&self, data: &Self::Data) -> Cow<str> {
Expand All @@ -30,33 +33,35 @@ pub trait Item: Sync + Send + 'static {
label.into()
}

fn weight(&self, _data: &Self::Data) -> u32 {
0
}
fn tie_breaker(&self, _data: &Self::Data) -> Self::TieBreaker;
}

impl Item for PathBuf {
/// Root prefix to strip.
type Data = PathBuf;

type TieBreaker = ();

fn format(&self, root_path: &Self::Data) -> Row {
self.strip_prefix(root_path)
.unwrap_or(self)
.to_string_lossy()
.into()
}

fn tie_breaker(&self, _data: &Self::Data) -> Self::TieBreaker {}
}

pub type MenuCallback<T> = Box<dyn Fn(&mut Editor, Option<&T>, MenuEvent)>;

pub struct Menu<T: Item> {
pub struct Menu<T: Item<TieBreaker = B>, B> {
options: Vec<T>,
editor_data: T::Data,

cursor: Option<usize>,

/// (index, score)
matches: Vec<(u32, u32)>,
/// (index, score, tie-breaker)
matches: Vec<(u32, u32, Option<B>)>,

widths: Vec<Constraint>,

Expand All @@ -68,7 +73,7 @@ pub struct Menu<T: Item> {
recalculate: bool,
}

impl<T: Item> Menu<T> {
impl<T: Item<TieBreaker = B>, B: Ord + std::marker::Copy> Menu<T, B> {
const LEFT_PADDING: usize = 1;

// TODO: it's like a slimmed down picker, share code? (picker = menu + prompt with different
Expand All @@ -78,7 +83,7 @@ impl<T: Item> Menu<T> {
editor_data: <T as Item>::Data,
callback_fn: impl Fn(&mut Editor, Option<&T>, MenuEvent) + 'static,
) -> Self {
let matches = (0..options.len() as u32).map(|i| (i, 0)).collect();
let matches = (0..options.len() as u32).map(|i| (i, 0, None)).collect();
Self {
options,
editor_data,
Expand All @@ -102,14 +107,14 @@ impl<T: Item> Menu<T> {
let mut buf = Vec::new();
let matches = self.options.iter().enumerate().filter_map(|(i, option)| {
let text = option.filter_text(&self.editor_data);
let weight = option.weight(&self.editor_data);
let breaker = option.tie_breaker(&self.editor_data);
pattern
.score(Utf32Str::new(&text, &mut buf), &mut matcher)
.map(|score| (i as u32, score as u32))
.map(|score| (i as u32, score as u32, Some(breaker)))
});
self.matches.extend(matches);
self.matches
.sort_unstable_by_key(|&(i, score)| (Reverse(score), i));
.sort_unstable_by_key(|&(i, score, breaker)| (Reverse(score), breaker, i));

// reset cursor position
self.cursor = None;
Expand Down Expand Up @@ -203,15 +208,15 @@ impl<T: Item> Menu<T> {
self.cursor.and_then(|cursor| {
self.matches
.get(cursor)
.map(|(index, _score)| &self.options[*index as usize])
.map(|(index, _score, _breaker)| &self.options[*index as usize])
})
}

pub fn selection_mut(&mut self) -> Option<&mut T> {
self.cursor.and_then(|cursor| {
self.matches
.get(cursor)
.map(|(index, _score)| &mut self.options[*index as usize])
.map(|(index, _score, _breaker)| &mut self.options[*index as usize])
})
}

Expand All @@ -224,7 +229,7 @@ impl<T: Item> Menu<T> {
}
}

impl<T: Item + PartialEq> Menu<T> {
impl<T: Item<TieBreaker = B> + PartialEq, B> Menu<T, B> {
pub fn replace_option(&mut self, old_option: T, new_option: T) {
for option in &mut self.options {
if old_option == *option {
Expand All @@ -237,7 +242,9 @@ impl<T: Item + PartialEq> Menu<T> {

use super::PromptEvent as MenuEvent;

impl<T: Item + 'static> Component for Menu<T> {
impl<T: Item<TieBreaker = B> + 'static, B: Ord + std::marker::Copy + 'static> Component
for Menu<T, B>
{
fn handle_event(&mut self, event: &Event, cx: &mut Context) -> EventResult {
let event = match event {
Event::Key(event) => *event,
Expand Down Expand Up @@ -332,7 +339,7 @@ impl<T: Item + 'static> Component for Menu<T> {
let options: Vec<_> = self
.matches
.iter()
.map(|(index, _score)| {
.map(|(index, _score, _brekaer)| {
// (index, self.options.get(*index).unwrap()) // get_unchecked
&self.options[*index as usize] // get_unchecked
})
Expand Down

0 comments on commit 56e4302

Please sign in to comment.