summaryrefslogtreecommitdiff
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.rs187
1 files changed, 102 insertions, 85 deletions
diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs
index a121a836..fff4792d 100644
--- a/helix-view/src/editor.rs
+++ b/helix-view/src/editor.rs
@@ -2,6 +2,7 @@ use crate::{
clipboard::{get_clipboard_provider, ClipboardProvider},
document::{Mode, SCRATCH_BUFFER_NAME},
graphics::{CursorKind, Rect},
+ input::KeyEvent,
theme::{self, Theme},
tree::{self, Tree},
Document, DocumentId, View, ViewId,
@@ -11,6 +12,7 @@ use futures_util::future;
use std::{
collections::{BTreeMap, HashMap},
io::stdin,
+ num::NonZeroUsize,
path::{Path, PathBuf},
pin::Pin,
sync::Arc,
@@ -18,7 +20,7 @@ use std::{
use tokio::time::{sleep, Duration, Instant, Sleep};
-use anyhow::Error;
+use anyhow::{bail, Error};
pub use helix_core::diagnostic::Severity;
pub use helix_core::register::Registers;
@@ -105,6 +107,8 @@ pub struct Config {
pub file_picker: FilePickerConfig,
/// Shape for cursor in each mode
pub cursor_shape: CursorShapeConfig,
+ /// Set to `true` to override automatic detection of terminal truecolor support in the event of a false negative. Defaults to `false`.
+ pub true_color: bool,
}
// Cursor shape is read and used on every rendered frame and so needs
@@ -141,7 +145,7 @@ impl Default for CursorShapeConfig {
}
}
-#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum LineNumber {
/// Show absolute line number
@@ -171,6 +175,7 @@ impl Default for Config {
auto_info: true,
file_picker: FilePickerConfig::default(),
cursor_shape: CursorShapeConfig::default(),
+ true_color: false,
}
}
}
@@ -190,11 +195,12 @@ impl std::fmt::Debug for Motion {
#[derive(Debug)]
pub struct Editor {
pub tree: Tree,
- pub next_document_id: usize,
+ pub next_document_id: DocumentId,
pub documents: BTreeMap<DocumentId, Document>,
pub count: Option<std::num::NonZeroUsize>,
pub selected_register: Option<char>,
pub registers: Registers,
+ pub macro_recording: Option<(char, Vec<KeyEvent>)>,
pub theme: Theme,
pub language_servers: helix_lsp::Registry,
pub clipboard_provider: Box<dyn ClipboardProvider>,
@@ -223,8 +229,8 @@ pub enum Action {
impl Editor {
pub fn new(
mut area: Rect,
- themes: Arc<theme::Loader>,
- config_loader: Arc<syntax::Loader>,
+ theme_loader: Arc<theme::Loader>,
+ syn_loader: Arc<syntax::Loader>,
config: Config,
) -> Self {
let language_servers = helix_lsp::Registry::new();
@@ -234,14 +240,15 @@ impl Editor {
Self {
tree: Tree::new(area),
- next_document_id: 0,
+ next_document_id: DocumentId::default(),
documents: BTreeMap::new(),
count: None,
selected_register: None,
- theme: themes.default(),
+ macro_recording: None,
+ theme: theme_loader.default(),
language_servers,
- syn_loader: config_loader,
- theme_loader: themes,
+ syn_loader,
+ theme_loader,
registers: Registers::default(),
clipboard_provider: get_clipboard_provider(),
status_msg: None,
@@ -297,14 +304,51 @@ impl Editor {
self._refresh();
}
- pub fn set_theme_from_name(&mut self, theme: &str) -> anyhow::Result<()> {
- use anyhow::Context;
- let theme = self
- .theme_loader
- .load(theme.as_ref())
- .with_context(|| format!("failed setting theme `{}`", theme))?;
- self.set_theme(theme);
- Ok(())
+ /// Refreshes the language server for a given document
+ pub fn refresh_language_server(&mut self, doc_id: DocumentId) -> Option<()> {
+ let doc = self.documents.get_mut(&doc_id)?;
+ doc.detect_language(Some(&self.theme), &self.syn_loader);
+ Self::launch_language_server(&mut self.language_servers, doc)
+ }
+
+ /// Launch a language server for a given document
+ fn launch_language_server(ls: &mut helix_lsp::Registry, doc: &mut Document) -> Option<()> {
+ // try to find a language server based on the language name
+ let language_server = doc.language.as_ref().and_then(|language| {
+ ls.get(language)
+ .map_err(|e| {
+ log::error!(
+ "Failed to initialize the LSP for `{}` {{ {} }}",
+ language.scope(),
+ e
+ )
+ })
+ .ok()
+ });
+ if let Some(language_server) = language_server {
+ // only spawn a new lang server if the servers aren't the same
+ if Some(language_server.id()) != doc.language_server().map(|server| server.id()) {
+ if let Some(language_server) = doc.language_server() {
+ tokio::spawn(language_server.text_document_did_close(doc.identifier()));
+ }
+ let language_id = doc
+ .language()
+ .and_then(|s| s.split('.').last()) // source.rust
+ .map(ToOwned::to_owned)
+ .unwrap_or_default();
+
+ // TODO: this now races with on_init code if the init happens too quickly
+ tokio::spawn(language_server.text_document_did_open(
+ doc.url().unwrap(),
+ doc.version(),
+ doc.text(),
+ language_id,
+ ));
+
+ doc.set_language_server(Some(language_server));
+ }
+ }
+ Some(())
}
fn _refresh(&mut self) {
@@ -358,7 +402,8 @@ impl Editor {
.tree
.traverse()
.any(|(_, v)| v.doc == doc.id && v.id != view.id);
- let view = view_mut!(self);
+
+ let (view, doc) = current!(self);
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`.
@@ -367,7 +412,16 @@ impl Editor {
} else {
let jump = (view.doc, doc.selection(view.id).clone());
view.jumps.push(jump);
- view.last_accessed_doc = Some(view.doc);
+ // Set last accessed doc if it is a different document
+ if doc.id != id {
+ view.last_accessed_doc = Some(view.doc);
+ // Set last modified doc if modified and last modified doc is different
+ if std::mem::take(&mut doc.modified_since_accessed)
+ && view.last_modified_docs[0] != Some(id)
+ {
+ view.last_modified_docs = [Some(view.doc), view.last_modified_docs[0]];
+ }
+ }
}
let view_id = view.id;
@@ -377,23 +431,22 @@ impl Editor {
}
Action::Load => {
let view_id = view!(self).id;
- if let Some(doc) = self.document_mut(id) {
- if doc.selections().is_empty() {
- doc.selections.insert(view_id, Selection::point(0));
- }
+ let doc = self.documents.get_mut(&id).unwrap();
+ if doc.selections().is_empty() {
+ doc.selections.insert(view_id, Selection::point(0));
}
return;
}
- Action::HorizontalSplit => {
- let view = View::new(id);
- let view_id = self.tree.split(view, Layout::Horizontal);
- // initialize selection for view
- let doc = self.documents.get_mut(&id).unwrap();
- doc.selections.insert(view_id, Selection::point(0));
- }
- Action::VerticalSplit => {
+ Action::HorizontalSplit | Action::VerticalSplit => {
let view = View::new(id);
- let view_id = self.tree.split(view, Layout::Vertical);
+ let view_id = self.tree.split(
+ view,
+ match action {
+ Action::HorizontalSplit => Layout::Horizontal,
+ Action::VerticalSplit => Layout::Vertical,
+ _ => unreachable!(),
+ },
+ );
// initialize selection for view
let doc = self.documents.get_mut(&id).unwrap();
doc.selections.insert(view_id, Selection::point(0));
@@ -403,16 +456,19 @@ impl Editor {
self._refresh();
}
- fn new_document(&mut self, mut document: Document) -> DocumentId {
- let id = DocumentId(self.next_document_id);
- self.next_document_id += 1;
- document.id = id;
- self.documents.insert(id, document);
+ /// Generate an id for a new document and register it.
+ fn new_document(&mut self, mut doc: Document) -> DocumentId {
+ let id = self.next_document_id;
+ // Safety: adding 1 from 1 is fine, probably impossible to reach usize max
+ self.next_document_id =
+ DocumentId(unsafe { NonZeroUsize::new_unchecked(self.next_document_id.0.get() + 1) });
+ doc.id = id;
+ self.documents.insert(id, doc);
id
}
- fn new_file_from_document(&mut self, action: Action, document: Document) -> DocumentId {
- let id = self.new_document(document);
+ fn new_file_from_document(&mut self, action: Action, doc: Document) -> DocumentId {
+ let id = self.new_document(doc);
self.switch(id, action);
id
}
@@ -428,54 +484,16 @@ impl Editor {
pub fn open(&mut self, path: PathBuf, action: Action) -> Result<DocumentId, Error> {
let path = helix_core::path::get_canonicalized_path(&path)?;
-
- let id = self
- .documents()
- .find(|doc| doc.path() == Some(&path))
- .map(|doc| doc.id);
+ let id = self.document_by_path(&path).map(|doc| doc.id);
let id = if let Some(id) = id {
id
} else {
let mut doc = Document::open(&path, None, Some(&self.theme), Some(&self.syn_loader))?;
- // try to find a language server based on the language name
- let language_server = doc.language.as_ref().and_then(|language| {
- self.language_servers
- .get(language)
- .map_err(|e| {
- log::error!(
- "Failed to initialize the LSP for `{}` {{ {} }}",
- language.scope(),
- e
- )
- })
- .ok()
- });
-
- if let Some(language_server) = language_server {
- let language_id = doc
- .language()
- .and_then(|s| s.split('.').last()) // source.rust
- .map(ToOwned::to_owned)
- .unwrap_or_default();
-
- // TODO: this now races with on_init code if the init happens too quickly
- tokio::spawn(language_server.text_document_did_open(
- doc.url().unwrap(),
- doc.version(),
- doc.text(),
- language_id,
- ));
-
- doc.set_language_server(Some(language_server));
- }
+ let _ = Self::launch_language_server(&mut self.language_servers, &mut doc);
- let id = DocumentId(self.next_document_id);
- self.next_document_id += 1;
- doc.id = id;
- self.documents.insert(id, doc);
- id
+ self.new_document(doc)
};
self.switch(id, action);
@@ -498,11 +516,11 @@ impl Editor {
pub fn close_document(&mut self, doc_id: DocumentId, force: bool) -> anyhow::Result<()> {
let doc = match self.documents.get(&doc_id) {
Some(doc) => doc,
- None => anyhow::bail!("document does not exist"),
+ None => bail!("document does not exist"),
};
if !force && doc.is_modified() {
- anyhow::bail!(
+ bail!(
"buffer {:?} is modified",
doc.relative_path()
.map(|path| path.to_string_lossy().to_string())
@@ -535,7 +553,7 @@ impl Editor {
// If the document we removed was visible in all views, we will have no more views. We don't
// want to close the editor just for a simple buffer close, so we need to create a new view
// containing either an existing document, or a brand new document.
- if self.tree.views().peekable().peek().is_none() {
+ if self.tree.views().next().is_none() {
let doc_id = self
.documents
.iter()
@@ -620,8 +638,7 @@ impl Editor {
}
pub fn cursor(&self) -> (Option<Position>, CursorKind) {
- let view = view!(self);
- let doc = &self.documents[&view.doc];
+ let (view, doc) = current_ref!(self);
let cursor = doc
.selection(view.id)
.primary()