aboutsummaryrefslogtreecommitdiff
path: root/helix-term/src/ui
diff options
context:
space:
mode:
Diffstat (limited to 'helix-term/src/ui')
-rw-r--r--helix-term/src/ui/completion.rs40
-rw-r--r--helix-term/src/ui/editor.rs99
-rw-r--r--helix-term/src/ui/mod.rs29
-rw-r--r--helix-term/src/ui/prompt.rs4
4 files changed, 129 insertions, 43 deletions
diff --git a/helix-term/src/ui/completion.rs b/helix-term/src/ui/completion.rs
index a55201ff..274330c0 100644
--- a/helix-term/src/ui/completion.rs
+++ b/helix-term/src/ui/completion.rs
@@ -154,8 +154,19 @@ impl Completion {
);
doc.apply(&transaction, view.id);
- if let Some(additional_edits) = &item.additional_text_edits {
- // gopls uses this to add extra imports
+ // apply additional edits, mostly used to auto import unqualified types
+ let resolved_additional_text_edits = if item.additional_text_edits.is_some() {
+ None
+ } else {
+ Completion::resolve_completion_item(doc, item.clone())
+ .and_then(|item| item.additional_text_edits)
+ };
+
+ if let Some(additional_edits) = item
+ .additional_text_edits
+ .as_ref()
+ .or_else(|| resolved_additional_text_edits.as_ref())
+ {
if !additional_edits.is_empty() {
let transaction = util::generate_transaction_from_edits(
doc.text(),
@@ -181,6 +192,31 @@ impl Completion {
completion
}
+ fn resolve_completion_item(
+ doc: &Document,
+ completion_item: lsp::CompletionItem,
+ ) -> Option<CompletionItem> {
+ let language_server = doc.language_server()?;
+ let completion_resolve_provider = language_server
+ .capabilities()
+ .completion_provider
+ .as_ref()?
+ .resolve_provider;
+ if completion_resolve_provider != Some(true) {
+ return None;
+ }
+
+ let future = language_server.resolve_completion_item(completion_item);
+ let response = helix_lsp::block_on(future);
+ match response {
+ Ok(completion_item) => Some(completion_item),
+ Err(err) => {
+ log::error!("execute LSP command: {}", err);
+ None
+ }
+ }
+ }
+
pub fn recompute_filter(&mut self, editor: &Editor) {
// recompute menu based on matches
let menu = self.popup.contents_mut();
diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs
index 6b015171..5b7e9075 100644
--- a/helix-term/src/ui/editor.rs
+++ b/helix-term/src/ui/editor.rs
@@ -7,7 +7,7 @@ use crate::{
};
use helix_core::{
- coords_at_pos,
+ coords_at_pos, encoding,
graphemes::{ensure_grapheme_boundary_next, next_grapheme_boundary, prev_grapheme_boundary},
movement::Direction,
syntax::{self, HighlightEvent},
@@ -566,21 +566,6 @@ impl EditorView {
}
surface.set_string(viewport.x + 5, viewport.y, progress, base_style);
- let rel_path = doc.relative_path();
- let path = rel_path
- .as_ref()
- .map(|p| p.to_string_lossy())
- .unwrap_or_else(|| SCRATCH_BUFFER_NAME.into());
-
- let title = format!("{}{}", path, if doc.is_modified() { "[+]" } else { "" });
- surface.set_stringn(
- viewport.x + 8,
- viewport.y,
- title,
- viewport.width.saturating_sub(6) as usize,
- base_style,
- );
-
//-------------------------------
// Right side of the status line.
//-------------------------------
@@ -654,6 +639,13 @@ impl EditorView {
base_style,
));
+ let enc = doc.encoding();
+ if enc != encoding::UTF_8 {
+ right_side_text
+ .0
+ .push(Span::styled(format!(" {} ", enc.name()), base_style));
+ }
+
// Render to the statusline.
surface.set_spans(
viewport.x
@@ -664,6 +656,31 @@ impl EditorView {
&right_side_text,
right_side_text.width() as u16,
);
+
+ //-------------------------------
+ // Middle / File path / Title
+ //-------------------------------
+ let title = {
+ let rel_path = doc.relative_path();
+ let path = rel_path
+ .as_ref()
+ .map(|p| p.to_string_lossy())
+ .unwrap_or_else(|| SCRATCH_BUFFER_NAME.into());
+ format!("{}{}", path, if doc.is_modified() { "[+]" } else { "" })
+ };
+
+ surface.set_string_truncated(
+ viewport.x + 8, // 8: 1 space + 3 char mode string + 1 space + 1 spinner + 1 space
+ viewport.y,
+ title,
+ viewport
+ .width
+ .saturating_sub(6)
+ .saturating_sub(right_side_text.width() as u16 + 1) as usize, // "+ 1": a space between the title and the selection info
+ base_style,
+ true,
+ true,
+ );
}
/// Handle events by looking them up in `self.keymaps`. Returns None
@@ -782,8 +799,9 @@ impl EditorView {
pub fn clear_completion(&mut self, editor: &mut Editor) {
self.completion = None;
+
// Clear any savepoints
- let (_, doc) = current!(editor);
+ let doc = doc_mut!(editor);
doc.savepoint = None;
editor.clear_idle_timer(); // don't retrigger
}
@@ -941,14 +959,18 @@ impl EditorView {
}
impl Component for EditorView {
- fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult {
- let mut cxt = commands::Context {
- editor: cx.editor,
+ fn handle_event(
+ &mut self,
+ event: Event,
+ context: &mut crate::compositor::Context,
+ ) -> EventResult {
+ let mut cx = commands::Context {
+ editor: context.editor,
count: None,
register: None,
callback: None,
on_next_key_callback: None,
- jobs: cx.jobs,
+ jobs: context.jobs,
};
match event {
@@ -958,18 +980,19 @@ impl Component for EditorView {
EventResult::Consumed(None)
}
Event::Key(key) => {
- cxt.editor.reset_idle_timer();
+ cx.editor.reset_idle_timer();
let mut key = KeyEvent::from(key);
canonicalize_key(&mut key);
+
// clear status
- cxt.editor.status_msg = None;
+ cx.editor.status_msg = None;
- let (_, doc) = current!(cxt.editor);
+ let doc = doc!(cx.editor);
let mode = doc.mode();
if let Some(on_next_key) = self.on_next_key.take() {
// if there's a command waiting input, do that first
- on_next_key(&mut cxt, key);
+ on_next_key(&mut cx, key);
} else {
match mode {
Mode::Insert => {
@@ -981,8 +1004,8 @@ impl Component for EditorView {
if let Some(completion) = &mut self.completion {
// use a fake context here
let mut cx = Context {
- editor: cxt.editor,
- jobs: cxt.jobs,
+ editor: cx.editor,
+ jobs: cx.jobs,
scroll: None,
};
let res = completion.handle_event(event, &mut cx);
@@ -992,40 +1015,40 @@ impl Component for EditorView {
if callback.is_some() {
// assume close_fn
- self.clear_completion(cxt.editor);
+ self.clear_completion(cx.editor);
}
}
}
// if completion didn't take the event, we pass it onto commands
if !consumed {
- self.insert_mode(&mut cxt, key);
+ self.insert_mode(&mut cx, key);
// lastly we recalculate completion
if let Some(completion) = &mut self.completion {
- completion.update(&mut cxt);
+ completion.update(&mut cx);
if completion.is_empty() {
- self.clear_completion(cxt.editor);
+ self.clear_completion(cx.editor);
}
}
}
}
- mode => self.command_mode(mode, &mut cxt, key),
+ mode => self.command_mode(mode, &mut cx, key),
}
}
- self.on_next_key = cxt.on_next_key_callback.take();
+ self.on_next_key = cx.on_next_key_callback.take();
// appease borrowck
- let callback = cxt.callback.take();
+ let callback = cx.callback.take();
// if the command consumed the last view, skip the render.
// on the next loop cycle the Application will then terminate.
- if cxt.editor.should_close() {
+ if cx.editor.should_close() {
return EventResult::Ignored;
}
- let (view, doc) = current!(cxt.editor);
- view.ensure_cursor_in_view(doc, cxt.editor.config.scrolloff);
+ let (view, doc) = current!(cx.editor);
+ view.ensure_cursor_in_view(doc, cx.editor.config.scrolloff);
// mode transitions
match (mode, doc.mode()) {
@@ -1054,7 +1077,7 @@ impl Component for EditorView {
EventResult::Consumed(callback)
}
- Event::Mouse(event) => self.handle_mouse_event(event, &mut cxt),
+ Event::Mouse(event) => self.handle_mouse_event(event, &mut cx),
}
}
diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs
index f57e2e2b..9ff9118f 100644
--- a/helix-term/src/ui/mod.rs
+++ b/helix-term/src/ui/mod.rs
@@ -174,7 +174,9 @@ pub mod completers {
use crate::ui::prompt::Completion;
use fuzzy_matcher::skim::SkimMatcherV2 as Matcher;
use fuzzy_matcher::FuzzyMatcher;
+ use helix_view::editor::Config;
use helix_view::theme;
+ use once_cell::sync::Lazy;
use std::borrow::Cow;
use std::cmp::Reverse;
@@ -208,6 +210,31 @@ pub mod completers {
names
}
+ pub fn setting(input: &str) -> Vec<Completion> {
+ static KEYS: Lazy<Vec<String>> = Lazy::new(|| {
+ serde_json::to_value(Config::default())
+ .unwrap()
+ .as_object()
+ .unwrap()
+ .keys()
+ .cloned()
+ .collect()
+ });
+
+ let matcher = Matcher::default();
+
+ let mut matches: Vec<_> = KEYS
+ .iter()
+ .filter_map(|name| matcher.fuzzy_match(name, input).map(|score| (name, score)))
+ .collect();
+
+ matches.sort_unstable_by_key(|(_file, score)| Reverse(*score));
+ matches
+ .into_iter()
+ .map(|(name, _)| ((0..), name.into()))
+ .collect()
+ }
+
pub fn filename(input: &str) -> Vec<Completion> {
filename_impl(input, |entry| {
let is_dir = entry.file_type().map_or(false, |entry| entry.is_dir());
@@ -256,7 +283,7 @@ pub mod completers {
let is_tilde = input.starts_with('~') && input.len() == 1;
let path = helix_core::path::expand_tilde(Path::new(input));
- let (dir, file_name) = if input.ends_with('/') {
+ let (dir, file_name) = if input.ends_with(std::path::MAIN_SEPARATOR) {
(path, None)
} else {
let file_name = path
diff --git a/helix-term/src/ui/prompt.rs b/helix-term/src/ui/prompt.rs
index 07e1b33c..0202de23 100644
--- a/helix-term/src/ui/prompt.rs
+++ b/helix-term/src/ui/prompt.rs
@@ -127,7 +127,7 @@ impl Prompt {
let mut char_position = char_indices
.iter()
.position(|(idx, _)| *idx == self.cursor)
- .unwrap_or_else(|| char_indices.len());
+ .unwrap_or(char_indices.len());
for _ in 0..rep {
// Skip any non-whitespace characters
@@ -473,7 +473,7 @@ impl Component for Prompt {
}
}
key!(Enter) => {
- if self.selection.is_some() && self.line.ends_with('/') {
+ if self.selection.is_some() && self.line.ends_with(std::path::MAIN_SEPARATOR) {
self.completion = (self.completion_fn)(&self.line);
self.exit_selection();
} else {