Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-view/src/editor.rs')
| -rw-r--r-- | helix-view/src/editor.rs | 315 |
1 files changed, 77 insertions, 238 deletions
diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 7f8cff9c..589d7c5a 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -4,7 +4,7 @@ use crate::{ document::{ DocumentOpenError, DocumentSavedEventFuture, DocumentSavedEventResult, Mode, SavePoint, }, - events::{DocumentDidClose, DocumentDidOpen, DocumentFocusLost}, + events::DocumentFocusLost, graphics::{CursorKind, Rect}, handlers::Handlers, info::Info, @@ -14,6 +14,7 @@ use crate::{ tree::{self, Tree}, Document, DocumentId, View, ViewId, }; +use dap::StackFrame; use helix_event::dispatch; use helix_vcs::DiffProviderRegistry; @@ -28,7 +29,7 @@ use std::{ collections::{BTreeMap, HashMap, HashSet}, fs, io::{self, stdin}, - num::{NonZeroU8, NonZeroUsize}, + num::NonZeroUsize, path::{Path, PathBuf}, pin::Pin, sync::Arc, @@ -44,14 +45,10 @@ use anyhow::{anyhow, bail, Error}; pub use helix_core::diagnostic::Severity; use helix_core::{ auto_pairs::AutoPairs, - diagnostic::DiagnosticProvider, - syntax::{ - self, - config::{AutoPairConfig, IndentationHeuristic, LanguageServerFeature, SoftWrap}, - }, + syntax::{self, AutoPairConfig, IndentationHeuristic, LanguageServerFeature, SoftWrap}, Change, LineEnding, Position, Range, Selection, Uri, NATIVE_LINE_ENDING, }; -use helix_dap::{self as dap, registry::DebugAdapterId}; +use helix_dap as dap; use helix_lsp::lsp; use helix_stdx::path::canonicalize; @@ -221,49 +218,6 @@ impl Default for FilePickerConfig { } } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "kebab-case", default, deny_unknown_fields)] -pub struct FileExplorerConfig { - /// IgnoreOptions - /// Enables ignoring hidden files. - /// Whether to hide hidden files in file explorer and global search results. Defaults to false. - pub hidden: bool, - /// Enables following symlinks. - /// Whether to follow symbolic links in file picker and file or directory completions. Defaults to false. - pub follow_symlinks: bool, - /// Enables reading ignore files from parent directories. Defaults to false. - pub parents: bool, - /// Enables reading `.ignore` files. - /// Whether to hide files listed in .ignore in file picker and global search results. Defaults to false. - pub ignore: bool, - /// Enables reading `.gitignore` files. - /// Whether to hide files listed in .gitignore in file picker and global search results. Defaults to false. - pub git_ignore: bool, - /// Enables reading global .gitignore, whose path is specified in git's config: `core.excludefile` option. - /// Whether to hide files listed in global .gitignore in file picker and global search results. Defaults to false. - pub git_global: bool, - /// Enables reading `.git/info/exclude` files. - /// Whether to hide files listed in .git/info/exclude in file picker and global search results. Defaults to false. - pub git_exclude: bool, - /// Whether to flatten single-child directories in file explorer. Defaults to true. - pub flatten_dirs: bool, -} - -impl Default for FileExplorerConfig { - fn default() -> Self { - Self { - hidden: false, - follow_symlinks: false, - parents: false, - ignore: false, - git_ignore: false, - git_global: false, - git_exclude: false, - flatten_dirs: true, - } - } -} - fn serialize_alphabet<S>(alphabet: &[char], serializer: S) -> Result<S::Ok, S::Error> where S: Serializer, @@ -321,9 +275,6 @@ pub struct Config { /// either absolute or relative to the current opened document or current working directory (if the buffer is not yet saved). /// Defaults to true. pub path_completion: bool, - /// Configures completion of words from open buffers. - /// Defaults to enabled with a trigger length of 7. - pub word_completion: WordCompletion, /// Automatic formatting on save. Defaults to true. pub auto_format: bool, /// Default register used for yank/paste. Defaults to '"' @@ -355,13 +306,9 @@ pub struct Config { /// Whether to instruct the LSP to replace the entire word when applying a completion /// or to only insert new text pub completion_replace: bool, - /// `true` if helix should automatically add a line comment token if you're currently in a comment - /// and press `enter`. - pub continue_comments: bool, /// Whether to display infoboxes. Defaults to true. pub auto_info: bool, pub file_picker: FilePickerConfig, - pub file_explorer: FileExplorerConfig, /// Configuration of the statusline elements pub statusline: StatusLineConfig, /// Shape for cursor in each mode @@ -392,16 +339,6 @@ pub struct Config { pub default_line_ending: LineEndingConfig, /// Whether to automatically insert a trailing line-ending on write if missing. Defaults to `true`. pub insert_final_newline: bool, - /// Whether to use atomic operations to write documents to disk. - /// This prevents data loss if the editor is interrupted while writing the file, but may - /// confuse some file watching/hot reloading programs. Defaults to `true`. - pub atomic_save: bool, - /// Whether to automatically remove all trailing line-endings after the final one on write. - /// Defaults to `false`. - pub trim_final_newlines: bool, - /// Whether to automatically remove all whitespace characters preceding line-endings on write. - /// Defaults to `false`. - pub trim_trailing_whitespace: bool, /// Enables smart tab pub smart_tab: Option<SmartTabConfig>, /// Draw border around popups. @@ -420,26 +357,10 @@ pub struct Config { pub end_of_line_diagnostics: DiagnosticFilter, // Set to override the default clipboard provider pub clipboard_provider: ClipboardProvider, - /// Whether to read settings from [EditorConfig](https://editorconfig.org) files. Defaults to - /// `true`. - pub editor_config: bool, - /// Whether to render rainbow colors for matching brackets. Defaults to `false`. - pub rainbow_brackets: bool, - /// Whether to enable Kitty Keyboard Protocol - pub kitty_keyboard_protocol: KittyKeyboardProtocolConfig, -} - -#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize, Clone, Copy)] -#[serde(rename_all = "kebab-case")] -pub enum KittyKeyboardProtocolConfig { - #[default] - Auto, - Disabled, - Enabled, } #[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Eq, PartialOrd, Ord)] -#[serde(default, rename_all = "kebab-case", deny_unknown_fields)] +#[serde(rename_all = "kebab-case", default)] pub struct SmartTabConfig { pub enable: bool, pub supersede_menu: bool, @@ -522,11 +443,6 @@ pub struct LspConfig { pub display_signature_help_docs: bool, /// Display inlay hints pub display_inlay_hints: bool, - /// Maximum displayed length of inlay hints (excluding the added trailing `…`). - /// If it's `None`, there's no limit - pub inlay_hints_length_limit: Option<NonZeroU8>, - /// Display document color swatches - pub display_color_swatches: bool, /// Whether to enable snippet support pub snippets: bool, /// Whether to include declaration in the goto reference query @@ -542,10 +458,8 @@ impl Default for LspConfig { auto_signature_help: true, display_signature_help_docs: true, display_inlay_hints: false, - inlay_hints_length_limit: None, snippets: true, goto_reference_include_declaration: true, - display_color_swatches: true, } } } @@ -567,8 +481,6 @@ pub struct StatusLineConfig { pub right: Vec<StatusLineElement>, pub separator: String, pub mode: ModeConfig, - pub diagnostics: Vec<Severity>, - pub workspace_diagnostics: Vec<Severity>, } impl Default for StatusLineConfig { @@ -593,8 +505,6 @@ impl Default for StatusLineConfig { ], separator: String::from("│"), mode: ModeConfig::default(), - diagnostics: vec![Severity::Warning, Severity::Error], - workspace_diagnostics: vec![Severity::Warning, Severity::Error], } } } @@ -647,9 +557,6 @@ pub enum StatusLineElement { /// The file line endings (CRLF or LF) FileLineEnding, - /// The file indentation style - FileIndentStyle, - /// The file type (language ID or "text") FileType, @@ -685,9 +592,6 @@ pub enum StatusLineElement { /// Indicator for selected register Register, - - /// The base of current working directory - CurrentWorkingDirectory, } // Cursor shape is read and used on every rendered frame and so needs @@ -1037,22 +941,6 @@ pub enum PopupBorderConfig { Menu, } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -#[serde(default, rename_all = "kebab-case", deny_unknown_fields)] -pub struct WordCompletion { - pub enable: bool, - pub trigger_length: NonZeroU8, -} - -impl Default for WordCompletion { - fn default() -> Self { - Self { - enable: true, - trigger_length: NonZeroU8::new(7).unwrap(), - } - } -} - impl Default for Config { fn default() -> Self { Self { @@ -1072,7 +960,6 @@ impl Default for Config { auto_pairs: AutoPairConfig::default(), auto_completion: true, path_completion: true, - word_completion: WordCompletion::default(), auto_format: true, default_yank_register: '"', auto_save: AutoSave::default(), @@ -1082,7 +969,6 @@ impl Default for Config { completion_trigger_len: 2, auto_info: true, file_picker: FilePickerConfig::default(), - file_explorer: FileExplorerConfig::default(), statusline: StatusLineConfig::default(), cursor_shape: CursorShapeConfig::default(), true_color: false, @@ -1101,23 +987,16 @@ impl Default for Config { }, text_width: 80, completion_replace: false, - continue_comments: true, workspace_lsp_roots: Vec::new(), default_line_ending: LineEndingConfig::default(), insert_final_newline: true, - atomic_save: true, - trim_final_newlines: false, - trim_trailing_whitespace: false, smart_tab: Some(SmartTabConfig::default()), popup_border: PopupBorderConfig::None, indent_heuristic: IndentationHeuristic::default(), jump_label_alphabet: ('a'..='z').collect(), inline_diagnostics: InlineDiagnosticsConfig::default(), - end_of_line_diagnostics: DiagnosticFilter::Enable(Severity::Hint), + end_of_line_diagnostics: DiagnosticFilter::Disable, clipboard_provider: ClipboardProvider::default(), - editor_config: true, - rainbow_brackets: false, - kitty_keyboard_protocol: Default::default(), } } } @@ -1146,8 +1025,6 @@ pub struct Breakpoint { use futures_util::stream::{Flatten, Once}; -type Diagnostics = BTreeMap<Uri, Vec<(lsp::Diagnostic, DiagnosticProvider)>>; - pub struct Editor { /// Current editing mode. pub mode: Mode, @@ -1167,10 +1044,11 @@ pub struct Editor { pub macro_recording: Option<(char, Vec<KeyEvent>)>, pub macro_replaying: Vec<char>, pub language_servers: helix_lsp::Registry, - pub diagnostics: Diagnostics, + pub diagnostics: BTreeMap<Uri, Vec<(lsp::Diagnostic, LanguageServerId)>>, pub diff_providers: DiffProviderRegistry, - pub debug_adapters: dap::registry::Registry, + pub debugger: Option<dap::Client>, + pub debugger_events: SelectAll<UnboundedReceiverStream<dap::Payload>>, pub breakpoints: HashMap<PathBuf, Vec<Breakpoint>>, pub syn_loader: Arc<ArcSwap<syntax::Loader>>, @@ -1197,7 +1075,7 @@ pub struct Editor { redraw_timer: Pin<Box<Sleep>>, last_motion: Option<Motion>, pub last_completion: Option<CompleteAction>, - last_cwd: Option<PathBuf>, + pub last_cwd: Option<PathBuf>, pub exit_code: i32, @@ -1228,7 +1106,7 @@ pub enum EditorEvent { DocumentSaved(DocumentSavedEventResult), ConfigEvent(ConfigEvent), LanguageServerMessage((LanguageServerId, Call)), - DebuggerEvent((DebugAdapterId, dap::Payload)), + DebuggerEvent(dap::Payload), IdleTimer, Redraw, } @@ -1313,9 +1191,10 @@ impl Editor { macro_replaying: Vec::new(), theme: theme_loader.default(), language_servers, - diagnostics: Diagnostics::new(), + diagnostics: BTreeMap::new(), diff_providers: DiffProviderRegistry::default(), - debug_adapters: dap::registry::Registry::new(), + debugger: None, + debugger_events: SelectAll::new(), breakpoints: HashMap::new(), syn_loader, theme_loader, @@ -1377,16 +1256,11 @@ impl Editor { /// Call if the config has changed to let the editor update all /// relevant members. - pub fn refresh_config(&mut self, old_config: &Config) { + pub fn refresh_config(&mut self) { let config = self.config(); self.auto_pairs = (&config.auto_pairs).into(); self.reset_idle_timer(); self._refresh(); - helix_event::dispatch(crate::events::ConfigDidChange { - editor: self, - old: old_config, - new: &config, - }) } pub fn clear_idle_timer(&mut self) { @@ -1459,7 +1333,7 @@ impl Editor { fn set_theme_impl(&mut self, theme: Theme, preview: ThemeAction) { // `ui.selection` is the only scope required to be able to render a theme. - if theme.find_highlight_exact("ui.selection").is_none() { + if theme.find_scope_index_exact("ui.selection").is_none() { self.set_error("Invalid theme: `ui.selection` required"); return; } @@ -1517,7 +1391,7 @@ impl Editor { continue; }; let edit = match helix_lsp::block_on(request) { - Ok(edit) => edit.unwrap_or_default(), + Ok(edit) => edit, Err(err) => { log::error!("invalid willRename response: {err:?}"); continue; @@ -1527,11 +1401,7 @@ impl Editor { log::error!("failed to apply workspace edit: {err:?}") } } - - if old_path.exists() { - fs::rename(old_path, &new_path)?; - } - + fs::rename(old_path, &new_path)?; if let Some(doc) = self.document_by_path(old_path) { self.set_doc_path(doc.id(), &new_path); } @@ -1542,7 +1412,9 @@ impl Editor { if !ls.is_initialized() { continue; } - ls.did_rename(old_path, &new_path, is_dir); + if let Some(notification) = ls.did_rename(old_path, &new_path, is_dir) { + tokio::spawn(notification); + }; } self.language_servers .file_event_handler @@ -1565,7 +1437,7 @@ impl Editor { } // if we are open in LSPs send did_close notification for language_server in doc.language_servers() { - language_server.text_document_did_close(doc.identifier()); + tokio::spawn(language_server.text_document_did_close(doc.identifier())); } } // we need to clear the list of language servers here so that @@ -1574,15 +1446,13 @@ impl Editor { // we have fully unregistered this document from its LS doc.language_servers.clear(); doc.set_path(Some(path)); - doc.detect_editor_config(); self.refresh_doc_language(doc_id) } pub fn refresh_doc_language(&mut self, doc_id: DocumentId) { - let loader = self.syn_loader.load(); + let loader = self.syn_loader.clone(); let doc = doc_mut!(self, &doc_id); - doc.detect_language(&loader); - doc.detect_editor_config(); + doc.detect_language(loader); doc.detect_indent_and_line_ending(); self.refresh_language_servers(doc_id); let doc = doc_mut!(self, &doc_id); @@ -1617,12 +1487,12 @@ impl Editor { if let helix_lsp::Error::ExecutableNotFound(err) = err { // Silence by default since some language servers might just not be installed log::debug!( - "Language server not found for `{}` {} {}", language.scope, lang, err, + "Language server not found for `{}` {} {}", language.scope(), lang, err, ); } else { log::error!( "Failed to initialize the language servers for `{}` - `{}` {{ {} }}", - language.scope, + language.scope(), lang, err ); @@ -1644,27 +1514,27 @@ impl Editor { doc.language_servers.iter().filter(|(name, doc_ls)| { language_servers .get(*name) - .is_none_or(|ls| ls.id() != doc_ls.id()) + .map_or(true, |ls| ls.id() != doc_ls.id()) }); for (_, language_server) in doc_language_servers_not_in_registry { - language_server.text_document_did_close(doc.identifier()); + tokio::spawn(language_server.text_document_did_close(doc.identifier())); } let language_servers_not_in_doc = language_servers.iter().filter(|(name, ls)| { doc.language_servers .get(*name) - .is_none_or(|doc_ls| ls.id() != doc_ls.id()) + .map_or(true, |doc_ls| ls.id() != doc_ls.id()) }); for (_, language_server) in language_servers_not_in_doc { // TODO: this now races with on_init code if the init happens too quickly - language_server.text_document_did_open( + tokio::spawn(language_server.text_document_did_open( doc_url.clone(), doc.version(), doc.text(), language_id.clone(), - ); + )); } doc.language_servers = language_servers; @@ -1841,10 +1711,7 @@ impl Editor { } pub fn new_file(&mut self, action: Action) -> DocumentId { - self.new_file_from_document( - action, - Document::default(self.config.clone(), self.syn_loader.clone()), - ) + self.new_file_from_document(action, Document::default(self.config.clone())) } pub fn new_file_from_stdin(&mut self, action: Action) -> Result<DocumentId, Error> { @@ -1853,7 +1720,6 @@ impl Editor { helix_core::Rope::default(), Some((encoding, has_bom)), self.config.clone(), - self.syn_loader.clone(), ); let doc_id = self.new_file_from_document(action, doc); let doc = doc_mut!(self, &doc_id); @@ -1867,14 +1733,10 @@ impl Editor { Ok(doc_id) } - pub fn document_id_by_path(&self, path: &Path) -> Option<DocumentId> { - self.document_by_path(path).map(|doc| doc.id) - } - // ??? possible use for integration tests pub fn open(&mut self, path: &Path, action: Action) -> Result<DocumentId, DocumentOpenError> { let path = helix_stdx::path::canonicalize(path); - let id = self.document_id_by_path(&path); + let id = self.document_by_path(&path).map(|doc| doc.id); let id = if let Some(id) = id { id @@ -1882,9 +1744,8 @@ impl Editor { let mut doc = Document::open( &path, None, - true, + Some(self.syn_loader.clone()), self.config.clone(), - self.syn_loader.clone(), )?; let diagnostics = @@ -1899,16 +1760,10 @@ impl Editor { let id = self.new_document(doc); self.launch_language_servers(id); - helix_event::dispatch(DocumentDidOpen { - editor: self, - doc: id, - }); - id }; self.switch(id, action); - Ok(id) } @@ -1922,7 +1777,7 @@ impl Editor { } pub fn close_document(&mut self, doc_id: DocumentId, force: bool) -> Result<(), CloseError> { - let doc = match self.documents.get(&doc_id) { + let doc = match self.documents.get_mut(&doc_id) { Some(doc) => doc, None => return Err(CloseError::DoesNotExist), }; @@ -1933,6 +1788,11 @@ impl Editor { // This will also disallow any follow-up writes self.saves.remove(&doc_id); + for language_server in doc.language_servers() { + // TODO: track error + tokio::spawn(language_server.text_document_did_close(doc.identifier())); + } + enum Action { Close(ViewId), ReplaceDoc(ViewId, DocumentId), @@ -1969,7 +1829,7 @@ impl Editor { } } - let doc = self.documents.remove(&doc_id).unwrap(); + self.documents.remove(&doc_id); // 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 @@ -1980,12 +1840,7 @@ impl Editor { .iter() .map(|(&doc_id, _)| doc_id) .next() - .unwrap_or_else(|| { - self.new_document(Document::default( - self.config.clone(), - self.syn_loader.clone(), - )) - }); + .unwrap_or_else(|| self.new_document(Document::default(self.config.clone()))); let view = View::new(doc_id, self.config().gutters.clone()); let view_id = self.tree.insert(view); let doc = doc_mut!(self, &doc_id); @@ -1995,8 +1850,6 @@ impl Editor { self._refresh(); - helix_event::dispatch(DocumentDidClose { editor: self, doc }); - Ok(()) } @@ -2044,29 +1897,28 @@ impl Editor { } pub fn focus(&mut self, view_id: ViewId) { - if self.tree.focus == view_id { - return; - } + let prev_id = std::mem::replace(&mut self.tree.focus, view_id); - // Reset mode to normal and ensure any pending changes are committed in the old document. - self.enter_normal_mode(); - let (view, doc) = current!(self); - doc.append_changes_to_history(view); - self.ensure_cursor_in_view(view_id); - // Update jumplist selections with new document changes. - for (view, _focused) in self.tree.views_mut() { + // if leaving the view: mode should reset and the cursor should be + // within view + if prev_id != view_id { + self.enter_normal_mode(); + self.ensure_cursor_in_view(view_id); + + // Update jumplist selections with new document changes. + for (view, _focused) in self.tree.views_mut() { + let doc = doc_mut!(self, &view.doc); + view.sync_changes(doc); + } + let view = view!(self, view_id); let doc = doc_mut!(self, &view.doc); - view.sync_changes(doc); + doc.mark_as_focused(); + let focus_lost = self.tree.get(prev_id).doc; + dispatch(DocumentFocusLost { + editor: self, + doc: focus_lost, + }); } - - let prev_id = std::mem::replace(&mut self.tree.focus, view_id); - doc_mut!(self).mark_as_focused(); - - let focus_lost = self.tree.get(prev_id).doc; - dispatch(DocumentFocusLost { - editor: self, - doc: focus_lost, - }); } pub fn focus_next(&mut self) { @@ -2136,7 +1988,7 @@ impl Editor { /// Returns all supported diagnostics for the document pub fn doc_diagnostics<'a>( language_servers: &'a helix_lsp::Registry, - diagnostics: &'a Diagnostics, + diagnostics: &'a BTreeMap<Uri, Vec<(lsp::Diagnostic, LanguageServerId)>>, document: &Document, ) -> impl Iterator<Item = helix_core::Diagnostic> + 'a { Editor::doc_diagnostics_with_filter(language_servers, diagnostics, document, |_, _| true) @@ -2146,9 +1998,9 @@ impl Editor { /// filtered by `filter` which is invocated with the raw `lsp::Diagnostic` and the language server id it came from pub fn doc_diagnostics_with_filter<'a>( language_servers: &'a helix_lsp::Registry, - diagnostics: &'a Diagnostics, + diagnostics: &'a BTreeMap<Uri, Vec<(lsp::Diagnostic, LanguageServerId)>>, document: &Document, - filter: impl Fn(&lsp::Diagnostic, &DiagnosticProvider) -> bool + 'a, + filter: impl Fn(&lsp::Diagnostic, LanguageServerId) -> bool + 'a, ) -> impl Iterator<Item = helix_core::Diagnostic> + 'a { let text = document.text().clone(); let language_config = document.language.clone(); @@ -2156,9 +2008,8 @@ impl Editor { .uri() .and_then(|uri| diagnostics.get(&uri)) .map(|diags| { - diags.iter().filter_map(move |(diagnostic, provider)| { - let server_id = provider.language_server_id()?; - let ls = language_servers.get_by_id(server_id)?; + diags.iter().filter_map(move |(diagnostic, lsp_id)| { + let ls = language_servers.get_by_id(*lsp_id)?; language_config .as_ref() .and_then(|c| { @@ -2168,12 +2019,12 @@ impl Editor { }) }) .and_then(|_| { - if filter(diagnostic, provider) { + if filter(diagnostic, *lsp_id) { Document::lsp_diagnostic_to_diagnostic( &text, language_config.as_deref(), diagnostic, - provider.clone(), + *lsp_id, ls.offset_encoding(), ) } else { @@ -2245,7 +2096,7 @@ impl Editor { Some(message) = self.language_servers.incoming.next() => { return EditorEvent::LanguageServerMessage(message) } - Some(event) = self.debug_adapters.incoming.next() => { + Some(event) = self.debugger_events.next() => { return EditorEvent::DebuggerEvent(event) } @@ -2321,8 +2172,10 @@ impl Editor { } } - pub fn current_stack_frame(&self) -> Option<&dap::StackFrame> { - self.debug_adapters.current_stack_frame() + pub fn current_stack_frame(&self) -> Option<&StackFrame> { + self.debugger + .as_ref() + .and_then(|debugger| debugger.current_stack_frame()) } /// Returns the id of a view that this doc contains a selection for, @@ -2348,34 +2201,20 @@ impl Editor { current_view.id } } - - pub fn set_cwd(&mut self, path: &Path) -> std::io::Result<()> { - self.last_cwd = helix_stdx::env::set_current_working_dir(path)?; - self.clear_doc_relative_paths(); - Ok(()) - } - - pub fn get_last_cwd(&mut self) -> Option<&Path> { - self.last_cwd.as_deref() - } } fn try_restore_indent(doc: &mut Document, view: &mut View) { use helix_core::{ - chars::char_is_whitespace, - line_ending::{line_end_char_index, str_is_line_ending}, - unicode::segmentation::UnicodeSegmentation, - Operation, Transaction, + chars::char_is_whitespace, line_ending::line_end_char_index, Operation, Transaction, }; fn inserted_a_new_blank_line(changes: &[Operation], pos: usize, line_end_pos: usize) -> bool { if let [Operation::Retain(move_pos), Operation::Insert(ref inserted_str), Operation::Retain(_)] = changes { - let mut graphemes = inserted_str.graphemes(true); move_pos + inserted_str.len() == pos - && graphemes.next().is_some_and(str_is_line_ending) - && graphemes.all(|g| g.chars().all(char_is_whitespace)) + && inserted_str.starts_with('\n') + && inserted_str.chars().skip(1).all(char_is_whitespace) && pos == line_end_pos // ensure no characters exists after current position } else { false |