aboutsummaryrefslogtreecommitdiff
path: root/helix-core
diff options
context:
space:
mode:
Diffstat (limited to 'helix-core')
-rw-r--r--helix-core/src/diagnostic.rs7
-rw-r--r--helix-core/src/history.rs46
-rw-r--r--helix-core/src/indent.rs29
-rw-r--r--helix-core/src/lib.rs4
-rw-r--r--helix-core/src/selection.rs5
-rw-r--r--helix-core/src/state.rs78
-rw-r--r--helix-core/src/syntax.rs6
-rw-r--r--helix-core/src/transaction.rs45
8 files changed, 78 insertions, 142 deletions
diff --git a/helix-core/src/diagnostic.rs b/helix-core/src/diagnostic.rs
new file mode 100644
index 00000000..96ed6746
--- /dev/null
+++ b/helix-core/src/diagnostic.rs
@@ -0,0 +1,7 @@
+use crate::Range;
+
+pub struct Diagnostic {
+ pub range: (usize, usize),
+ pub line: usize,
+ pub message: String,
+}
diff --git a/helix-core/src/history.rs b/helix-core/src/history.rs
index e6d9a738..66445525 100644
--- a/helix-core/src/history.rs
+++ b/helix-core/src/history.rs
@@ -57,37 +57,31 @@ impl History {
self.cursor == 0
}
- pub fn undo(&mut self, state: &mut State) {
+ // TODO: I'd like to pass Transaction by reference but it fights with the borrowck
+
+ pub fn undo(&mut self) -> Option<Transaction> {
if self.at_root() {
// We're at the root of undo, nothing to do.
- return;
+ return None;
}
let current_revision = &self.revisions[self.cursor];
- // TODO: pass the return value through? It should always succeed
- let success = current_revision.revert.apply(state);
-
- if !success {
- panic!("Failed to apply undo!");
- }
-
self.cursor = current_revision.parent;
+
+ Some(current_revision.revert.clone())
}
- pub fn redo(&mut self, state: &mut State) {
+ pub fn redo(&mut self) -> Option<Transaction> {
let current_revision = &self.revisions[self.cursor];
// for now, simply pick the latest child (linear undo / redo)
if let Some((index, transaction)) = current_revision.children.last() {
- let success = transaction.apply(state);
-
- if !success {
- panic!("Failed to apply redo!");
- }
-
self.cursor = *index;
+
+ return Some(transaction.clone());
}
+ None
}
}
@@ -120,17 +114,27 @@ mod test {
assert_eq!("hello 世界!", state.doc());
// ---
+ fn undo(history: &mut History, state: &mut State) {
+ if let Some(transaction) = history.undo() {
+ transaction.apply(state);
+ }
+ }
+ fn redo(history: &mut History, state: &mut State) {
+ if let Some(transaction) = history.redo() {
+ transaction.apply(state);
+ }
+ }
- history.undo(&mut state);
+ undo(&mut history, &mut state);
assert_eq!("hello world!", state.doc());
- history.redo(&mut state);
+ redo(&mut history, &mut state);
assert_eq!("hello 世界!", state.doc());
- history.undo(&mut state);
- history.undo(&mut state);
+ undo(&mut history, &mut state);
+ undo(&mut history, &mut state);
assert_eq!("hello", state.doc());
// undo at root is a no-op
- history.undo(&mut state);
+ undo(&mut history, &mut state);
assert_eq!("hello", state.doc());
}
}
diff --git a/helix-core/src/indent.rs b/helix-core/src/indent.rs
index 2e1a095e..6b9a1ab1 100644
--- a/helix-core/src/indent.rs
+++ b/helix-core/src/indent.rs
@@ -111,17 +111,17 @@ fn find_first_non_whitespace_char(state: &State, line_num: usize) -> usize {
start
}
-fn suggested_indent_for_line(state: &State, line_num: usize) -> usize {
+fn suggested_indent_for_line(syntax: Option<&Syntax>, state: &State, line_num: usize) -> usize {
let line = state.doc.line(line_num);
let current = indent_level_for_line(line);
let start = find_first_non_whitespace_char(state, line_num);
- suggested_indent_for_pos(state, start)
+ suggested_indent_for_pos(syntax, state, start)
}
-pub fn suggested_indent_for_pos(state: &State, pos: usize) -> usize {
- if let Some(syntax) = &state.syntax {
+pub fn suggested_indent_for_pos(syntax: Option<&Syntax>, state: &State, pos: usize) -> usize {
+ if let Some(syntax) = syntax {
let byte_start = state.doc.char_to_byte(pos);
let node = get_highest_syntax_node_at_bytepos(syntax, byte_start);
@@ -163,13 +163,18 @@ mod test {
",
);
- let mut state = State::new(doc);
- state.set_language("source.rust", &[]);
-
- assert_eq!(suggested_indent_for_line(&state, 0), 0); // mod
- assert_eq!(suggested_indent_for_line(&state, 1), 1); // fn
- assert_eq!(suggested_indent_for_line(&state, 2), 2); // 1 + 1
- assert_eq!(suggested_indent_for_line(&state, 4), 1); // }
- assert_eq!(suggested_indent_for_line(&state, 5), 0); // }
+ let state = State::new(doc);
+ // TODO: set_language
+ let language_config = crate::syntax::LOADER
+ .language_config_for_scope("source.rust")
+ .unwrap();
+ let highlight_config = language_config.highlight_config(&[]).unwrap().unwrap();
+ let syntax = Syntax::new(&state.doc, highlight_config.clone());
+
+ assert_eq!(suggested_indent_for_line(Some(&syntax), &state, 0), 0); // mod
+ assert_eq!(suggested_indent_for_line(Some(&syntax), &state, 1), 1); // fn
+ assert_eq!(suggested_indent_for_line(Some(&syntax), &state, 2), 2); // 1 + 1
+ assert_eq!(suggested_indent_for_line(Some(&syntax), &state, 4), 1); // }
+ assert_eq!(suggested_indent_for_line(Some(&syntax), &state, 5), 0); // }
}
}
diff --git a/helix-core/src/lib.rs b/helix-core/src/lib.rs
index 62d23a10..ddf1439c 100644
--- a/helix-core/src/lib.rs
+++ b/helix-core/src/lib.rs
@@ -1,4 +1,5 @@
#![allow(unused)]
+mod diagnostic;
pub mod graphemes;
mod history;
pub mod indent;
@@ -22,7 +23,8 @@ pub use selection::Range;
pub use selection::Selection;
pub use syntax::Syntax;
+pub use diagnostic::Diagnostic;
pub use history::History;
pub use state::State;
-pub use transaction::{Assoc, Change, ChangeSet, Transaction};
+pub use transaction::{Assoc, Change, ChangeSet, Operation, Transaction};
diff --git a/helix-core/src/selection.rs b/helix-core/src/selection.rs
index 13c820f1..9413fead 100644
--- a/helix-core/src/selection.rs
+++ b/helix-core/src/selection.rs
@@ -179,6 +179,11 @@ impl Selection {
}
}
+ /// Constructs a selection holding a single cursor.
+ pub fn point(pos: usize) -> Self {
+ Self::single(pos, pos)
+ }
+
#[must_use]
pub fn new(ranges: SmallVec<[Range; 1]>, primary_index: usize) -> Self {
fn normalize(mut ranges: SmallVec<[Range; 1]>, mut primary_index: usize) -> Selection {
diff --git a/helix-core/src/state.rs b/helix-core/src/state.rs
index 1b0a67ae..4d531aa0 100644
--- a/helix-core/src/state.rs
+++ b/helix-core/src/state.rs
@@ -1,33 +1,14 @@
use crate::graphemes::{nth_next_grapheme_boundary, nth_prev_grapheme_boundary, RopeGraphemes};
use crate::syntax::LOADER;
-use crate::{ChangeSet, Position, Range, Rope, RopeSlice, Selection, Syntax};
+use crate::{ChangeSet, Diagnostic, Position, Range, Rope, RopeSlice, Selection, Syntax};
use anyhow::Error;
-use std::path::PathBuf;
-
-#[derive(Copy, Clone, PartialEq, Eq, Hash)]
-pub enum Mode {
- Normal,
- Insert,
- Goto,
-}
-
/// A state represents the current editor state of a single buffer.
+#[derive(Clone)]
pub struct State {
// TODO: fields should be private but we need to refactor commands.rs first
- /// Path to file on disk.
- pub path: Option<PathBuf>,
pub doc: Rope,
pub selection: Selection,
- pub mode: Mode,
-
- pub restore_cursor: bool,
-
- //
- pub syntax: Option<Syntax>,
- /// Pending changes since last history commit.
- pub changes: ChangeSet,
- pub old_state: Option<(Rope, Selection)>,
}
#[derive(Copy, Clone, PartialEq, Eq)]
@@ -46,57 +27,12 @@ pub enum Granularity {
impl State {
#[must_use]
pub fn new(doc: Rope) -> Self {
- let changes = ChangeSet::new(&doc);
- let old_state = Some((doc.clone(), Selection::single(0, 0)));
-
Self {
- path: None,
doc,
selection: Selection::single(0, 0),
- mode: Mode::Normal,
- restore_cursor: false,
- syntax: None,
- changes,
- old_state,
}
}
- // TODO: passing scopes here is awkward
- pub fn load(path: PathBuf, scopes: &[String]) -> Result<Self, Error> {
- use std::{env, fs::File, io::BufReader, path::PathBuf};
- let _current_dir = env::current_dir()?;
-
- let doc = Rope::from_reader(BufReader::new(File::open(path.clone())?))?;
-
- // TODO: create if not found
-
- let mut state = Self::new(doc);
-
- if let Some(language_config) = LOADER.language_config_for_file_name(path.as_path()) {
- let highlight_config = language_config.highlight_config(scopes).unwrap().unwrap();
- // TODO: config.configure(scopes) is now delayed, is that ok?
-
- let syntax = Syntax::new(&state.doc, highlight_config.clone());
-
- state.syntax = Some(syntax);
- };
-
- state.path = Some(path);
-
- Ok(state)
- }
-
- pub fn set_language(&mut self, scope: &str, scopes: &[String]) {
- if let Some(language_config) = LOADER.language_config_for_scope(scope) {
- let highlight_config = language_config.highlight_config(scopes).unwrap().unwrap();
- // TODO: config.configure(scopes) is now delayed, is that ok?
-
- let syntax = Syntax::new(&self.doc, highlight_config.clone());
-
- self.syntax = Some(syntax);
- };
- }
-
// TODO: doc/selection accessors
// TODO: be able to take either Rope or RopeSlice
@@ -110,16 +46,6 @@ impl State {
&self.selection
}
- #[inline]
- pub fn mode(&self) -> Mode {
- self.mode
- }
-
- #[inline]
- pub fn path(&self) -> Option<&PathBuf> {
- self.path.as_ref()
- }
-
// pub fn doc<R>(&self, range: R) -> RopeSlice
// where
// R: std::ops::RangeBounds<usize>,
diff --git a/helix-core/src/syntax.rs b/helix-core/src/syntax.rs
index 02903637..70d42c47 100644
--- a/helix-core/src/syntax.rs
+++ b/helix-core/src/syntax.rs
@@ -62,11 +62,15 @@ impl LanguageConfiguration {
})
.map(Option::as_ref)
}
+
+ pub fn scope(&self) -> &str {
+ &self.scope
+ }
}
use once_cell::sync::Lazy;
-pub(crate) static LOADER: Lazy<Loader> = Lazy::new(Loader::init);
+pub static LOADER: Lazy<Loader> = Lazy::new(Loader::init);
pub struct Loader {
// highlight_names ?
diff --git a/helix-core/src/transaction.rs b/helix-core/src/transaction.rs
index 6f3956aa..f1cb2ca1 100644
--- a/helix-core/src/transaction.rs
+++ b/helix-core/src/transaction.rs
@@ -5,8 +5,9 @@ use std::convert::TryFrom;
/// (from, to, replacement)
pub type Change = (usize, usize, Option<Tendril>);
+// TODO: pub(crate)
#[derive(Debug, Clone, PartialEq, Eq)]
-pub(crate) enum Operation {
+pub enum Operation {
/// Move cursor by n characters.
Retain(usize),
/// Delete n characters.
@@ -40,6 +41,12 @@ impl ChangeSet {
}
// TODO: from iter
+ //
+
+ #[doc(hidden)] // used by lsp to convert to LSP changes
+ pub fn changes(&self) -> &[Operation] {
+ &self.changes
+ }
#[must_use]
fn len_after(&self) -> usize {
@@ -351,22 +358,6 @@ pub struct Transaction {
// scroll_into_view
}
-/// Like std::mem::replace() except it allows the replacement value to be mapped from the
-/// original value.
-pub fn take_with<T, F>(mut_ref: &mut T, closure: F)
-where
- F: FnOnce(T) -> T,
-{
- use std::{panic, ptr};
-
- unsafe {
- let old_t = ptr::read(mut_ref);
- let new_t = panic::catch_unwind(panic::AssertUnwindSafe(|| closure(old_t)))
- .unwrap_or_else(|_| ::std::process::abort());
- ptr::write(mut_ref, new_t);
- }
-}
-
impl Transaction {
/// Create a new, empty transaction.
pub fn new(state: &mut State) -> Self {
@@ -376,29 +367,21 @@ impl Transaction {
}
}
+ pub fn changes(&self) -> &ChangeSet {
+ &self.changes
+ }
+
/// Returns true if applied successfully.
pub fn apply(&self, state: &mut State) -> bool {
if !self.changes.is_empty() {
- // TODO: also avoid mapping the selection if not necessary
-
- let old_doc = state.doc().clone();
-
// apply changes to the document
if !self.changes.apply(&mut state.doc) {
return false;
}
-
- // Compose this transaction with the previous one
- take_with(&mut state.changes, |changes| {
- changes.compose(self.changes.clone()).unwrap()
- });
-
- if let Some(syntax) = &mut state.syntax {
- // TODO: no unwrap
- syntax.update(&old_doc, &state.doc, &self.changes).unwrap();
- }
}
+ // TODO: also avoid mapping the selection if not necessary
+
// update the selection: either take the selection specified in the transaction, or map the
// current selection through changes.
state.selection = self