use crate::ui::{menu, Markdown, Menu, Popup, PromptEvent};
use crate::{
compositor::{Component, Context, Event, EventResult},
handlers::completion::{
trigger_auto_completion, CompletionItem, CompletionResponse, LspCompletionItem,
ResolveHandler,
},
};
use helix_core::snippets::{ActiveSnippet, RenderedSnippet, Snippet};
use helix_core::{self as core, chars, fuzzy::MATCHER, Change, Transaction};
use helix_lsp::{lsp, util, OffsetEncoding};
use helix_view::icons::ICONS;
use helix_view::{
editor::CompleteAction,
handlers::lsp::SignatureHelpInvoked,
theme::{Color, Modifier, Style},
ViewId,
};
use helix_view::{graphics::Rect, Document, Editor};
use nucleo::{
pattern::{Atom, AtomKind, CaseMatching, Normalization},
Config, Utf32Str,
};
use tui::text::Spans;
use tui::{buffer::Buffer as Surface, text::Span};
use std::cmp::Reverse;
impl menu::Item for CompletionItem {
type Data = Style;
fn format(&self, dir_style: &Self::Data) -> menu::Row {
let deprecated = match self {
CompletionItem::Lsp(LspCompletionItem { item, .. }) => {
item.deprecated.unwrap_or_default()
|| item
.tags
.as_ref()
.is_some_and(|tags| tags.contains(&lsp::CompletionItemTag::DEPRECATED))
}
CompletionItem::Other(_) => false,
};
let label = match self {
CompletionItem::Lsp(LspCompletionItem { item, .. }) => item.label.as_str(),
CompletionItem::Other(core::CompletionItem { label, .. }) => label,
};
let mut kind = match self {
CompletionItem::Lsp(LspCompletionItem { item, .. }) => match item.kind {
Some(lsp::CompletionItemKind::TEXT) => "text".into(),
Some(lsp::CompletionItemKind::METHOD) => "method".into(),
Some(lsp::CompletionItemKind::FUNCTION) => "function".into(),
Some(lsp::CompletionItemKind::CONSTRUCTOR) => "constructor".into(),
Some(lsp::CompletionItemKind::FIELD) => "field".into(),
Some(lsp::CompletionItemKind::VARIABLE) => "variable".into(),
Some(lsp::CompletionItemKind::CLASS) => "class".into(),
Some(lsp::CompletionItemKind::INTERFACE) => "interface".into(),
Some(lsp::CompletionItemKind::MODULE) => "module".into(),
Some(lsp::CompletionItemKind::PROPERTY) => "property".into(),
Some(lsp::CompletionItemKind::UNIT) => "unit".into(),
Some(lsp::CompletionItemKind::VALUE) => "value".into(),
Some(lsp::CompletionItemKind::ENUM) => "enum".into(),
Some(lsp::CompletionItemKind::KEYWORD) => "keyword".into(),
Some(lsp::CompletionItemKind::SNIPPET) => "snippet".into(),
Some(lsp::CompletionItemKind::COLOR) => item
.documentation
.as_ref()
.and_then(|docs| {
let text = match docs {
lsp::Documentation::String(text) => text,
lsp::Documentation::MarkupContent(lsp::MarkupContent {
value, ..
}) => value,
};
// Language servers which send Color completion items tend to include a 6
// digit hex code at the end for the color. The extra 1 digit is for the '#'
text.get(text.len().checked_sub(7)?..)
})
.and_then(Color::from_hex)
.map_or("color".into(), |color| {
let icons = ICONS.load();
Spans::from(vec![
Span::raw("color "),
Span::styled(
icons.kind().color().glyph().to_string(),
Style::default().fg(color),
),
])
}),
Some(lsp::CompletionItemKind::FILE) => "file".into(),
Some(lsp::CompletionItemKind::REFERENCE) => "reference".into(),
Some(lsp::CompletionItemKind::FOLDER) => "folder".into(),
Some(lsp::CompletionItemKind::ENUM_MEMBER) => "enum_member".into(),
Some(lsp::CompletionItemKind::CONSTANT) => "constant".into(),
Some(lsp::CompletionItemKind::STRUCT) => "struct".into(),
Some(lsp::CompletionItemKind::EVENT) => "event".into(),
Some(lsp::CompletionItemKind::OPERATOR) => "operator".into(),
Some(lsp::CompletionItemKind::TYPE_PARAMETER) => "type_param".into(),
Some(kind) => {
log::error!("Received unknown completion item kind: {:?}", kind);
"".into()
}
None => "".into(),
},
CompletionItem::Other(core::CompletionItem { kind, .. }) => kind.as_ref().into(),
};
let icons = ICONS.load();
let name = &kind.0[0].content;
let is_folder = kind.0[0].content == "folder";
if let Some(icon) = icons.kind().get(name) {
kind.0[0].content = format!("{icon} {name}").into();
if let Some(style) = icon.color().map(|color| Style::default().fg(color)) {
kind.0[0].style = style;
} else if is_folder {
kind.0[0].style = *dir_style;
}
} else {
kind.0[0].content = format!("{name}").into();
}
let label = Span::styled(
label,
if deprecated {
Style::default().add_modifier(Modifier::CROSSED_OUT)
} else if is_folder {
*dir_style
} else {
Style::default()
},
);
menu::Row::new([menu::Cell::from(label), menu::Cell::from(kind)])
}
}
/// Wraps a Menu.
pub struct Completion {
popup: Popup