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.rs222
1 files changed, 50 insertions, 172 deletions
diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs
index 7f8cff9c..dfade86b 100644
--- a/helix-view/src/editor.rs
+++ b/helix-view/src/editor.rs
@@ -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,
@@ -45,13 +46,10 @@ 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 +219,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 +276,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 '"'
@@ -361,7 +313,6 @@ pub struct Config {
/// 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,10 +343,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,
@@ -423,19 +370,6 @@ pub struct Config {
/// 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)]
@@ -522,9 +456,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
@@ -542,7 +473,6 @@ 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 +497,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 +521,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 +573,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 +608,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 +957,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 +976,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 +985,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,
@@ -1105,7 +1007,6 @@ impl Default for Config {
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()),
@@ -1113,11 +1014,9 @@ impl Default for Config {
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(),
}
}
}
@@ -1170,7 +1069,8 @@ pub struct Editor {
pub diagnostics: Diagnostics,
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>>,
@@ -1228,7 +1128,7 @@ pub enum EditorEvent {
DocumentSaved(DocumentSavedEventResult),
ConfigEvent(ConfigEvent),
LanguageServerMessage((LanguageServerId, Call)),
- DebuggerEvent((DebugAdapterId, dap::Payload)),
+ DebuggerEvent(dap::Payload),
IdleTimer,
Redraw,
}
@@ -1315,7 +1215,8 @@ impl Editor {
language_servers,
diagnostics: Diagnostics::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 +1278,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 +1355,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;
}
@@ -1527,11 +1423,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);
}
@@ -1579,9 +1471,9 @@ impl Editor {
}
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_language(loader);
doc.detect_editor_config();
doc.detect_indent_and_line_ending();
self.refresh_language_servers(doc_id);
@@ -1617,12 +1509,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,7 +1536,7 @@ 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 {
@@ -1654,7 +1546,7 @@ impl Editor {
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 {
@@ -1841,10 +1733,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 +1742,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);
@@ -1882,9 +1770,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 =
@@ -1929,6 +1816,7 @@ impl Editor {
if !force && doc.is_modified() {
return Err(CloseError::BufferModified(doc.display_name().into_owned()));
}
+ let doc = self.documents.remove(&doc_id).unwrap();
// This will also disallow any follow-up writes
self.saves.remove(&doc_id);
@@ -1969,8 +1857,6 @@ impl Editor {
}
}
- let doc = self.documents.remove(&doc_id).unwrap();
-
// 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.
@@ -1980,12 +1866,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);
@@ -2044,29 +1925,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) {
@@ -2245,7 +2125,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 +2201,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,
@@ -2362,20 +2244,16 @@ impl Editor {
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