diff --git a/helix-core/src/state.rs b/helix-core/src/state.rs index 65194b38..d597fcbb 100644 --- a/helix-core/src/state.rs +++ b/helix-core/src/state.rs @@ -198,9 +198,6 @@ impl State { granularity: Granularity, count: usize, ) -> Selection { - // move all selections according to normal cursor move semantics by collapsing it - // into cursors and moving them vertically - self.selection .transform(|range| self.move_range(range, dir, granularity, count, false)) } @@ -255,7 +252,6 @@ fn move_vertically( let pos = pos_at_coords(text, Position::new(new_line, new_col)); let mut range = Range::new(if extend { range.anchor } else { pos }, pos); - use std::convert::TryInto; range.horiz = Some(horiz); range } diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index c82724a5..dfa819db 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -137,7 +137,7 @@ impl Application { .tree .views() .map(|(view, _key)| view) - .find(|view| view.doc.path == path); + .find(|view| view.doc.path() == path.as_ref()); if let Some(view) = view { let doc = view.doc.text().slice(..); diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 104b86b7..bf6f0c88 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -442,7 +442,7 @@ pub fn delete_selection(cx: &mut Context) { let doc = cx.doc(); _delete_selection(doc); - append_changes_to_history(doc); + doc.append_changes_to_history(); } pub fn change_selection(cx: &mut Context) { @@ -492,7 +492,6 @@ pub fn append_mode(cx: &mut Context) { enter_insert_mode(doc); doc.restore_cursor = true; - // TODO: as transaction let text = doc.text().slice(..); let selection = doc.selection().transform(|range| { // TODO: to() + next char @@ -510,7 +509,6 @@ pub fn command_mode(cx: &mut Context) { let prompt = Prompt::new( ":".to_owned(), |_input: &str| { - // TODO: i need this duplicate list right now to avoid borrow checker issues let command_list = vec![ "q".to_string(), "o".to_string(), @@ -650,40 +648,12 @@ pub fn open_below(cx: &mut Context) { // O inserts a new line before each line with a selection -fn append_changes_to_history(doc: &mut Document) { - if doc.changes.is_empty() { - return; - } - - // TODO: change -> change -> undo -> change -> change fails, probably old_state needs reset - - let new_changeset = ChangeSet::new(doc.text()); - let changes = std::mem::replace(&mut doc.changes, new_changeset); - // Instead of doing this messy merge we could always commit, and based on transaction - // annotations either add a new layer or compose into the previous one. - let transaction = Transaction::from(changes).with_selection(doc.selection().clone()); - - // increment document version - // TODO: needs to happen on undo/redo too - doc.version += 1; - - // TODO: trigger lsp/documentDidChange with changes - - // HAXX: we need to reconstruct the state as it was before the changes.. - let old_state = doc.old_state.take().expect("no old_state available"); - - // TODO: take transaction by value? - doc.history.commit_revision(&transaction, &old_state); - - // TODO: notify LSP of changes -} - pub fn normal_mode(cx: &mut Context) { let doc = cx.doc(); doc.mode = Mode::Normal; - append_changes_to_history(doc); + doc.append_changes_to_history(); // if leaving append mode, move cursor back by 1 if doc.restore_cursor { @@ -848,7 +818,7 @@ pub fn paste(cx: &mut Context) { }; doc.apply(&transaction); - append_changes_to_history(doc); + doc.append_changes_to_history(); } } @@ -884,7 +854,7 @@ pub fn indent(cx: &mut Context) { }), ); doc.apply(&transaction); - append_changes_to_history(doc); + doc.append_changes_to_history(); } pub fn unindent(cx: &mut Context) { @@ -917,7 +887,7 @@ pub fn unindent(cx: &mut Context) { let transaction = Transaction::change(&doc.state, changes.into_iter()); doc.apply(&transaction); - append_changes_to_history(doc); + doc.append_changes_to_history(); } // @@ -1010,7 +980,7 @@ pub fn completion(cx: &mut Context) { let transaction = util::generate_transaction_from_edits(&doc.state, vec![edit]); doc.apply(&transaction); - // TODO: append_changes_to_history(doc); if not in insert mode? + // TODO: doc.append_changes_to_history(); if not in insert mode? } _ => (), }; diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index 72a2710d..dd7b14fc 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -17,7 +17,7 @@ pub enum Mode { pub struct Document { pub state: State, // rope + selection /// File path on disk. - pub path: Option, + path: Option, /// Current editing mode. pub mode: Mode, @@ -26,13 +26,16 @@ pub struct Document { /// Tree-sitter AST tree pub syntax: Option, /// Corresponding language scope name. Usually `source.`. - pub language: Option, + language: Option, /// Pending changes since last history commit. - pub changes: ChangeSet, - pub old_state: Option, - pub history: History, - pub version: i32, // should be usize? + changes: ChangeSet, + /// State at last commit. Used for calculating reverts. + old_state: Option, + /// Undo tree. + history: History, + /// Current document version, incremented at each change. + version: i32, // should be usize? pub diagnostics: Vec, pub language_server: Option>, @@ -90,23 +93,8 @@ impl Document { let mut doc = Self::new(State::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(&doc.state.doc, highlight_config.clone()); - - doc.syntax = Some(syntax); - // TODO: maybe just keep an Arc<> pointer to the language_config? - doc.language = Some(language_config.scope().to_string()); - - // TODO: this ties lsp support to tree-sitter enabled languages for now. Language - // config should use Option to let us have non-tree-sitter configs. - - // TODO: circular dep: view <-> lsp - // helix_lsp::REGISTRY; - // view should probably depend on lsp - }; + let language_config = LOADER.language_config_for_file_name(path.as_path()); + doc.set_language(language_config, scopes); // canonicalize path to absolute value doc.path = Some(std::fs::canonicalize(path)?); @@ -140,17 +128,35 @@ impl Document { } // and_then notify save } - pub fn set_language(&mut self, scope: &str, scopes: &[String]) { - if let Some(language_config) = LOADER.language_config_for_scope(scope) { + pub fn set_language( + &mut self, + language_config: Option>, + scopes: &[String], + ) { + if let Some(language_config) = language_config { + // TODO: maybe just keep an Arc<> pointer to the language_config? + self.language = Some(language_config.scope().to_string()); + + // TODO: this ties lsp support to tree-sitter enabled languages for now. Language + // config should use Option to let us have non-tree-sitter configs. + 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.state.doc, highlight_config.clone()); self.syntax = Some(syntax); + } else { + self.syntax = None; + self.language = None; }; } + pub fn set_language2(&mut self, scope: &str, scopes: &[String]) { + let language_config = LOADER.language_config_for_scope(scope); + self.set_language(language_config, scopes); + } + pub fn set_language_server(&mut self, language_server: Option>) { self.language_server = language_server; } @@ -238,11 +244,44 @@ impl Document { false } + pub fn append_changes_to_history(&mut self) { + if self.changes.is_empty() { + return; + } + + // TODO: change -> change -> undo -> change -> change fails, probably old_state needs reset + + let new_changeset = ChangeSet::new(self.text()); + let changes = std::mem::replace(&mut self.changes, new_changeset); + // Instead of doing this messy merge we could always commit, and based on transaction + // annotations either add a new layer or compose into the previous one. + let transaction = Transaction::from(changes).with_selection(self.selection().clone()); + + // increment document version + self.version += 1; + + // HAXX: we need to reconstruct the state as it was before the changes.. + let old_state = self.old_state.take().expect("no old_state available"); + + self.history.commit_revision(&transaction, &old_state); + } + #[inline] pub fn mode(&self) -> Mode { self.mode } + #[inline] + /// Corresponding language scope name. Usually `source.`. + pub fn language(&self) -> Option<&str> { + self.language.as_ref().map(String::as_str) + } + + #[inline] + pub fn version(&self) -> i32 { + self.version + } + #[inline] pub fn path(&self) -> Option<&PathBuf> { self.path.as_ref() diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 24cd8ef9..b70a6abd 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -37,9 +37,8 @@ impl Editor { // 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, &executor)); + .language() + .and_then(|language| self.language_servers.get(language, &executor)); if let Some(language_server) = language_server { // TODO: do this everywhere @@ -47,7 +46,7 @@ impl Editor { smol::block_on(language_server.text_document_did_open( doc.url().unwrap(), - doc.version, + doc.version(), doc.text(), )) .unwrap();