aboutsummaryrefslogtreecommitdiff
path: root/helix-view/src/editor.rs
diff options
context:
space:
mode:
Diffstat (limited to 'helix-view/src/editor.rs')
-rw-r--r--helix-view/src/editor.rs124
1 files changed, 94 insertions, 30 deletions
diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs
index 60864e9e..591e0492 100644
--- a/helix-view/src/editor.rs
+++ b/helix-view/src/editor.rs
@@ -2,7 +2,7 @@ use crate::{
clipboard::{get_clipboard_provider, ClipboardProvider},
graphics::{CursorKind, Rect},
theme::{self, Theme},
- tree::Tree,
+ tree::{self, Tree},
Document, DocumentId, View, ViewId,
};
@@ -12,6 +12,7 @@ use tokio_stream::wrappers::UnboundedReceiverStream;
use std::{
collections::HashMap,
+ collections::BTreeMap,
path::{Path, PathBuf},
pin::Pin,
sync::Arc,
@@ -19,8 +20,6 @@ use std::{
use tokio::time::{sleep, Duration, Instant, Sleep};
-use slotmap::SlotMap;
-
use anyhow::Error;
pub use helix_core::diagnostic::Severity;
@@ -63,6 +62,9 @@ pub struct Config {
/// Time in milliseconds since last keypress before idle timers trigger. Used for autocompletion, set to 0 for instant. Defaults to 400ms.
#[serde(skip_serializing, deserialize_with = "deserialize_duration_millis")]
pub idle_timeout: Duration,
+ pub completion_trigger_len: u8,
+ /// Whether to display infoboxes. Defaults to true.
+ pub auto_info: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
@@ -92,14 +94,29 @@ impl Default for Config {
auto_pairs: true,
auto_completion: true,
idle_timeout: Duration::from_millis(400),
+ completion_trigger_len: 2,
+ auto_info: true,
}
}
}
+pub struct Motion(pub Box<dyn Fn(&mut Editor)>);
+impl Motion {
+ pub fn run(&self, e: &mut Editor) {
+ (self.0)(e)
+ }
+}
+impl std::fmt::Debug for Motion {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.write_str("motion")
+ }
+}
+
#[derive(Debug)]
pub struct Editor {
pub tree: Tree,
- pub documents: SlotMap<DocumentId, Document>,
+ pub next_document_id: usize,
+ pub documents: BTreeMap<DocumentId, Document>,
pub count: Option<std::num::NonZeroUsize>,
pub selected_register: Option<char>,
pub registers: Registers,
@@ -124,6 +141,7 @@ pub struct Editor {
pub config: Config,
pub idle_timer: Pin<Box<Sleep>>,
+ pub last_motion: Option<Motion>,
}
#[derive(Debug, Copy, Clone)]
@@ -148,7 +166,8 @@ impl Editor {
Self {
tree: Tree::new(area),
- documents: SlotMap::with_key(),
+ next_document_id: 0,
+ documents: BTreeMap::new(),
count: None,
selected_register: None,
theme: themes.default(),
@@ -166,6 +185,7 @@ impl Editor {
clipboard_provider: get_clipboard_provider(),
status_msg: None,
idle_timer: Box::pin(sleep(config.idle_timeout)),
+ last_motion: None,
config,
}
}
@@ -221,7 +241,7 @@ impl Editor {
fn _refresh(&mut self) {
for (view, _) in self.tree.views_mut() {
- let doc = &self.documents[view.doc];
+ let doc = &self.documents[&view.doc];
view.ensure_cursor_in_view(doc, self.config.scrolloff)
}
}
@@ -230,22 +250,38 @@ impl Editor {
use crate::tree::Layout;
use helix_core::Selection;
- if !self.documents.contains_key(id) {
+ if !self.documents.contains_key(&id) {
log::error!("cannot switch to document that does not exist (anymore)");
return;
}
match action {
Action::Replace => {
- let view = view!(self);
- let jump = (
- view.doc,
- self.documents[view.doc].selection(view.id).clone(),
- );
-
+ let (view, doc) = current_ref!(self);
+ // If the current view is an empty scratch buffer and is not displayed in any other views, delete it.
+ // Boolean value is determined before the call to `view_mut` because the operation requires a borrow
+ // of `self.tree`, which is mutably borrowed when `view_mut` is called.
+ let remove_empty_scratch = !doc.is_modified()
+ // If the buffer has no path and is not modified, it is an empty scratch buffer.
+ && doc.path().is_none()
+ // If the buffer we are changing to is not this buffer
+ && id != doc.id
+ // Ensure the buffer is not displayed in any other splits.
+ && !self
+ .tree
+ .traverse()
+ .any(|(_, v)| v.doc == doc.id && v.id != view.id);
let view = view_mut!(self);
- view.jumps.push(jump);
- view.last_accessed_doc = Some(view.doc);
+ if remove_empty_scratch {
+ // Copy `doc.id` into a variable before calling `self.documents.remove`, which requires a mutable
+ // borrow, invalidating direct access to `doc.id`.
+ let id = doc.id;
+ self.documents.remove(&id);
+ } else {
+ let jump = (view.doc, doc.selection(view.id).clone());
+ view.jumps.push(jump);
+ view.last_accessed_doc = Some(view.doc);
+ }
view.doc = id;
view.offset = Position::default();
@@ -272,14 +308,14 @@ impl Editor {
let view = View::new(id);
let view_id = self.tree.split(view, Layout::Horizontal);
// initialize selection for view
- let doc = &mut self.documents[id];
+ let doc = self.documents.get_mut(&id).unwrap();
doc.selections.insert(view_id, Selection::point(0));
}
Action::VerticalSplit => {
let view = View::new(id);
let view_id = self.tree.split(view, Layout::Vertical);
// initialize selection for view
- let doc = &mut self.documents[id];
+ let doc = self.documents.get_mut(&id).unwrap();
doc.selections.insert(view_id, Selection::point(0));
}
}
@@ -288,9 +324,11 @@ impl Editor {
}
pub fn new_file(&mut self, action: Action) -> DocumentId {
- let doc = Document::default();
- let id = self.documents.insert(doc);
- self.documents[id].id = id;
+ let id = DocumentId(self.next_document_id);
+ self.next_document_id += 1;
+ let mut doc = Document::default();
+ doc.id = id;
+ self.documents.insert(id, doc);
self.switch(id, action);
id
}
@@ -313,7 +351,11 @@ impl Editor {
self.language_servers
.get(language)
.map_err(|e| {
- log::error!("Failed to get LSP, {}, for `{}`", e, language.scope())
+ log::error!(
+ "Failed to initialize the LSP for `{}` {{ {} }}",
+ language.scope(),
+ e
+ )
})
.ok()
});
@@ -336,8 +378,10 @@ impl Editor {
doc.set_language_server(Some(language_server));
}
- let id = self.documents.insert(doc);
- self.documents[id].id = id;
+ let id = DocumentId(self.next_document_id);
+ self.next_document_id += 1;
+ doc.id = id;
+ self.documents.insert(id, doc);
id
};
@@ -348,16 +392,20 @@ impl Editor {
pub fn close(&mut self, id: ViewId, close_buffer: bool) {
let view = self.tree.get(self.tree.focus);
// remove selection
- self.documents[view.doc].selections.remove(&id);
+ self.documents
+ .get_mut(&view.doc)
+ .unwrap()
+ .selections
+ .remove(&id);
if close_buffer {
// get around borrowck issues
- let doc = &self.documents[view.doc];
+ let doc = &self.documents[&view.doc];
if let Some(language_server) = doc.language_server() {
tokio::spawn(language_server.text_document_did_close(doc.identifier()));
}
- self.documents.remove(view.doc);
+ self.documents.remove(&view.doc);
}
self.tree.remove(id);
@@ -374,24 +422,40 @@ impl Editor {
self.tree.focus_next();
}
+ pub fn focus_right(&mut self) {
+ self.tree.focus_direction(tree::Direction::Right);
+ }
+
+ pub fn focus_left(&mut self) {
+ self.tree.focus_direction(tree::Direction::Left);
+ }
+
+ pub fn focus_up(&mut self) {
+ self.tree.focus_direction(tree::Direction::Up);
+ }
+
+ pub fn focus_down(&mut self) {
+ self.tree.focus_direction(tree::Direction::Down);
+ }
+
pub fn should_close(&self) -> bool {
self.tree.is_empty()
}
pub fn ensure_cursor_in_view(&mut self, id: ViewId) {
let view = self.tree.get_mut(id);
- let doc = &self.documents[view.doc];
+ let doc = &self.documents[&view.doc];
view.ensure_cursor_in_view(doc, self.config.scrolloff)
}
#[inline]
pub fn document(&self, id: DocumentId) -> Option<&Document> {
- self.documents.get(id)
+ self.documents.get(&id)
}
#[inline]
pub fn document_mut(&mut self, id: DocumentId) -> Option<&mut Document> {
- self.documents.get_mut(id)
+ self.documents.get_mut(&id)
}
#[inline]
@@ -416,7 +480,7 @@ impl Editor {
pub fn cursor(&self) -> (Option<Position>, CursorKind) {
let view = view!(self);
- let doc = &self.documents[view.doc];
+ let doc = &self.documents[&view.doc];
let cursor = doc
.selection(view.id)
.primary()