From ed23057ff8e01404ab608682445b4f293b6142ed Mon Sep 17 00:00:00 2001 From: Omnikar Date: Sat, 6 Nov 2021 11:57:14 -0400 Subject: Launch with defaults upon invalid config/theme (#982) * Launch with defaults upon invalid config/theme * Startup message if there is a problematic config * Statusline error if trying to switch to an invalid theme * Use serde `deny_unknown_fields` for config--- helix-view/src/editor.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'helix-view/src/editor.rs') diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 6aa8b04d..17cd3d7b 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -34,7 +34,7 @@ where } #[derive(Debug, Clone, PartialEq, Deserialize)] -#[serde(rename_all = "kebab-case", default)] +#[serde(rename_all = "kebab-case", default, deny_unknown_fields)] pub struct Config { /// Padding to keep between the edge of the screen and the cursor when scrolling. Defaults to 5. pub scrolloff: usize, @@ -195,6 +195,12 @@ impl Editor { } pub fn set_theme(&mut self, theme: Theme) { + // `ui.selection` is the only scope required to be able to render a theme. + if theme.find_scope_index("ui.selection").is_none() { + self.set_error("Invalid theme: `ui.selection` required".to_owned()); + return; + } + let scopes = theme.scopes(); for config in self .syn_loader -- cgit v1.2.3-70-g09d2 From 29e684941389f949491efe4d9807a0edd1facee3 Mon Sep 17 00:00:00 2001 From: CossonLeo Date: Mon, 8 Nov 2021 23:17:54 +0800 Subject: Add LSP rename_symbol (space-r) (#1011) improve apply_workspace_edit--- helix-lsp/src/client.rs | 27 +++++++ helix-term/src/commands.rs | 188 ++++++++++++++++++++++++++++++++++++--------- helix-term/src/keymap.rs | 1 + helix-view/src/editor.rs | 6 ++ 4 files changed, 187 insertions(+), 35 deletions(-) (limited to 'helix-view/src/editor.rs') diff --git a/helix-lsp/src/client.rs b/helix-lsp/src/client.rs index b810feef..271fd9d5 100644 --- a/helix-lsp/src/client.rs +++ b/helix-lsp/src/client.rs @@ -257,6 +257,12 @@ impl Client { content_format: Some(vec![lsp::MarkupKind::Markdown]), ..Default::default() }), + rename: Some(lsp::RenameClientCapabilities { + dynamic_registration: Some(false), + prepare_support: Some(false), + prepare_support_default_behavior: None, + honors_change_annotations: Some(false), + }), code_action: Some(lsp::CodeActionClientCapabilities { code_action_literal_support: Some(lsp::CodeActionLiteralSupport { code_action_kind: lsp::CodeActionKindLiteralSupport { @@ -773,4 +779,25 @@ impl Client { self.call::(params) } + + pub async fn rename_symbol( + &self, + text_document: lsp::TextDocumentIdentifier, + position: lsp::Position, + new_name: String, + ) -> anyhow::Result { + let params = lsp::RenameParams { + text_document_position: lsp::TextDocumentPositionParams { + text_document, + position, + }, + new_name, + work_done_progress_params: lsp::WorkDoneProgressParams { + work_done_token: None, + }, + }; + + let response = self.request::(params).await?; + Ok(response.unwrap_or_default()) + } } diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 5f091775..245fbe4e 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -23,7 +23,7 @@ use helix_view::{ use anyhow::{anyhow, bail, Context as _}; use helix_lsp::{ - lsp, + block_on, lsp, util::{lsp_pos_to_pos, lsp_range_to_range, pos_to_lsp_pos, range_to_lsp_range}, OffsetEncoding, }; @@ -339,6 +339,7 @@ impl Command { shell_append_output, "Append output of shell command after each selection", shell_keep_pipe, "Filter selections with shell predicate", suspend, "Suspend", + rename_symbol, "Rename symbol", ); } @@ -2749,14 +2750,104 @@ pub fn code_action(cx: &mut Context) { ) } +pub fn apply_document_resource_op(op: &lsp::ResourceOp) -> std::io::Result<()> { + use lsp::ResourceOp; + use std::fs; + match op { + ResourceOp::Create(op) => { + let path = op.uri.to_file_path().unwrap(); + let ignore_if_exists = if let Some(options) = &op.options { + !options.overwrite.unwrap_or(false) && options.ignore_if_exists.unwrap_or(false) + } else { + false + }; + if ignore_if_exists && path.exists() { + Ok(()) + } else { + fs::write(&path, []) + } + } + ResourceOp::Delete(op) => { + let path = op.uri.to_file_path().unwrap(); + if path.is_dir() { + let recursive = if let Some(options) = &op.options { + options.recursive.unwrap_or(false) + } else { + false + }; + if recursive { + fs::remove_dir_all(&path) + } else { + fs::remove_dir(&path) + } + } else if path.is_file() { + fs::remove_file(&path) + } else { + Ok(()) + } + } + ResourceOp::Rename(op) => { + let from = op.old_uri.to_file_path().unwrap(); + let to = op.new_uri.to_file_path().unwrap(); + let ignore_if_exists = if let Some(options) = &op.options { + !options.overwrite.unwrap_or(false) && options.ignore_if_exists.unwrap_or(false) + } else { + false + }; + if ignore_if_exists && to.exists() { + Ok(()) + } else { + fs::rename(&from, &to) + } + } + } +} + fn apply_workspace_edit( editor: &mut Editor, offset_encoding: OffsetEncoding, workspace_edit: &lsp::WorkspaceEdit, ) { + let mut apply_edits = |uri: &helix_lsp::Url, text_edits: Vec| { + let path = uri + .to_file_path() + .expect("unable to convert URI to filepath"); + + let current_view_id = view!(editor).id; + let doc_id = editor.open(path, Action::Load).unwrap(); + let doc = editor + .document_mut(doc_id) + .expect("Document for document_changes not found"); + + // Need to determine a view for apply/append_changes_to_history + let selections = doc.selections(); + let view_id = if selections.contains_key(¤t_view_id) { + // use current if possible + current_view_id + } else { + // Hack: we take the first available view_id + selections + .keys() + .next() + .copied() + .expect("No view_id available") + }; + + let transaction = helix_lsp::util::generate_transaction_from_edits( + doc.text(), + text_edits, + offset_encoding, + ); + doc.apply(&transaction, view_id); + doc.append_changes_to_history(view_id); + }; + if let Some(ref changes) = workspace_edit.changes { log::debug!("workspace changes: {:?}", changes); - editor.set_error(String::from("Handling workspace_edit.changes is not implemented yet, see https://github.com/helix-editor/helix/issues/183")); + for (uri, text_edits) in changes { + let text_edits = text_edits.to_vec(); + apply_edits(uri, text_edits); + } return; // Not sure if it works properly, it'll be safer to just panic here to avoid breaking some parts of code on which code actions will be used // TODO: find some example that uses workspace changes, and test it @@ -2774,30 +2865,6 @@ fn apply_workspace_edit( match document_changes { lsp::DocumentChanges::Edits(document_edits) => { for document_edit in document_edits { - let path = document_edit - .text_document - .uri - .to_file_path() - .expect("unable to convert URI to filepath"); - let current_view_id = view!(editor).id; - let doc = editor - .document_by_path_mut(path) - .expect("Document for document_changes not found"); - - // Need to determine a view for apply/append_changes_to_history - let selections = doc.selections(); - let view_id = if selections.contains_key(¤t_view_id) { - // use current if possible - current_view_id - } else { - // Hack: we take the first available view_id - selections - .keys() - .next() - .copied() - .expect("No view_id available") - }; - let edits = document_edit .edits .iter() @@ -2809,19 +2876,33 @@ fn apply_workspace_edit( }) .cloned() .collect(); - - let transaction = helix_lsp::util::generate_transaction_from_edits( - doc.text(), - edits, - offset_encoding, - ); - doc.apply(&transaction, view_id); - doc.append_changes_to_history(view_id); + apply_edits(&document_edit.text_document.uri, edits); } } lsp::DocumentChanges::Operations(operations) => { log::debug!("document changes - operations: {:?}", operations); - editor.set_error(String::from("Handling document operations is not implemented yet, see https://github.com/helix-editor/helix/issues/183")); + for operateion in operations { + match operateion { + lsp::DocumentChangeOperation::Op(op) => { + apply_document_resource_op(op).unwrap(); + } + + lsp::DocumentChangeOperation::Edit(document_edit) => { + let edits = document_edit + .edits + .iter() + .map(|edit| match edit { + lsp::OneOf::Left(text_edit) => text_edit, + lsp::OneOf::Right(annotated_text_edit) => { + &annotated_text_edit.text_edit + } + }) + .cloned() + .collect(); + apply_edits(&document_edit.text_document.uri, edits); + } + } + } } } } @@ -4982,3 +5063,40 @@ fn add_newline_impl(cx: &mut Context, open: Open) { doc.apply(&transaction, view.id); doc.append_changes_to_history(view.id); } + +fn rename_symbol(cx: &mut Context) { + let prompt = Prompt::new( + "Rename to: ".into(), + None, + |_input: &str| Vec::new(), + move |cx: &mut compositor::Context, input: &str, event: PromptEvent| { + if event != PromptEvent::Validate { + return; + } + + log::debug!("renaming to: {:?}", input); + + let (view, doc) = current!(cx.editor); + let language_server = match doc.language_server() { + Some(language_server) => language_server, + None => return, + }; + + let offset_encoding = language_server.offset_encoding(); + + let pos = pos_to_lsp_pos( + doc.text(), + doc.selection(view.id) + .primary() + .cursor(doc.text().slice(..)), + offset_encoding, + ); + + let task = language_server.rename_symbol(doc.identifier(), pos, input.to_string()); + let edits = block_on(task).unwrap_or_default(); + log::debug!("Edits from LSP: {:?}", edits); + apply_workspace_edit(&mut cx.editor, offset_encoding, &edits); + }, + ); + cx.push_layer(Box::new(prompt)); +} diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index ce50f0ab..d497401f 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -584,6 +584,7 @@ impl Default for Keymaps { "R" => replace_selections_with_clipboard, "/" => global_search, "k" => hover, + "r" => rename_symbol, }, "z" => { "View" "z" | "c" => align_view_center, diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 17cd3d7b..631dcf0c 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -287,6 +287,12 @@ impl Editor { return; } 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)); + } + } return; } Action::HorizontalSplit => { -- cgit v1.2.3-70-g09d2 From cf831b1a65625f29d6e1bc12483a45c1adc8dff4 Mon Sep 17 00:00:00 2001 From: Jason Hansen Date: Tue, 9 Nov 2021 18:53:14 -0700 Subject: Allow piping from stdin into a buffer on startup (#996) * Allow piping from stdin into a buffer on startup * Refactor * Don't allow piping into new buffer on macOS * Update helix-term/src/application.rs Co-authored-by: Blaž Hrastnik * Update helix-term/src/application.rs Co-authored-by: Blaž Hrastnik * Fix Co-authored-by: Blaž Hrastnik --- helix-term/src/application.rs | 14 ++++++++++++-- helix-view/src/editor.rs | 17 +++++++++++++---- 2 files changed, 25 insertions(+), 6 deletions(-) (limited to 'helix-view/src/editor.rs') diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index f1884199..b04eef0d 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -7,7 +7,7 @@ use crate::{args::Args, compositor::Compositor, config::Config, job::Jobs, ui}; use log::{error, warn}; use std::{ - io::{stdout, Write}, + io::{stdin, stdout, Write}, sync::Arc, time::{Duration, Instant}, }; @@ -17,6 +17,7 @@ use anyhow::Error; use crossterm::{ event::{DisableMouseCapture, EnableMouseCapture, Event, EventStream}, execute, terminal, + tty::IsTty, }; #[cfg(not(windows))] use { @@ -134,8 +135,17 @@ impl Application { } editor.set_status(format!("Loaded {} files.", nr_of_files)); } - } else { + } else if stdin().is_tty() { editor.new_file(Action::VerticalSplit); + } else if cfg!(target_os = "macos") { + // On Linux and Windows, we allow the output of a command to be piped into the new buffer. + // This doesn't currently work on macOS because of the following issue: + // https://github.com/crossterm-rs/crossterm/issues/500 + anyhow::bail!("Piping into helix-term is currently not supported on macOS"); + } else { + editor + .new_file_from_stdin(Action::VerticalSplit) + .unwrap_or_else(|_| editor.new_file(Action::VerticalSplit)); } editor.set_theme(theme); diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 631dcf0c..7650d217 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -9,6 +9,7 @@ use crate::{ use futures_util::future; use std::{ collections::BTreeMap, + io::stdin, path::{Path, PathBuf}, pin::Pin, sync::Arc, @@ -314,16 +315,24 @@ impl Editor { self._refresh(); } - pub fn new_file(&mut self, action: Action) -> DocumentId { + fn new_file_from_document(&mut self, action: Action, mut document: Document) -> DocumentId { let id = DocumentId(self.next_document_id); self.next_document_id += 1; - let mut doc = Document::default(); - doc.id = id; - self.documents.insert(id, doc); + document.id = id; + self.documents.insert(id, document); self.switch(id, action); id } + pub fn new_file(&mut self, action: Action) -> DocumentId { + self.new_file_from_document(action, Document::default()) + } + + pub fn new_file_from_stdin(&mut self, action: Action) -> Result { + let (rope, encoding) = crate::document::from_reader(&mut stdin(), None)?; + Ok(self.new_file_from_document(action, Document::from(rope, Some(encoding)))) + } + pub fn open(&mut self, path: PathBuf, action: Action) -> Result { let path = helix_core::path::get_canonicalized_path(&path)?; -- cgit v1.2.3-70-g09d2 From 87e61a0894d6af1838c5d288fae83279004026fb Mon Sep 17 00:00:00 2001 From: Cole Helbling Date: Sun, 14 Nov 2021 20:06:12 -0800 Subject: helix-term/commands: implement cquit (#1096) This allows you to exit helix with an exit code, e.g. `:cq 2`.--- helix-term/src/application.rs | 4 ++-- helix-term/src/commands.rs | 26 ++++++++++++++++++++++++++ helix-term/src/main.rs | 12 +++++++++--- helix-view/src/editor.rs | 3 +++ 4 files changed, 40 insertions(+), 5 deletions(-) (limited to 'helix-view/src/editor.rs') diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index b04eef0d..2969a9e5 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -599,7 +599,7 @@ impl Application { Ok(()) } - pub async fn run(&mut self) -> Result<(), Error> { + pub async fn run(&mut self) -> Result { self.claim_term().await?; // Exit the alternate screen and disable raw mode before panicking @@ -622,6 +622,6 @@ impl Application { self.restore_term()?; - Ok(()) + Ok(self.editor.exit_code) } } diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index ab62c1aa..8c0a005c 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -2042,6 +2042,25 @@ mod cmd { quit_all_impl(&mut cx.editor, args, event, true) } + fn cquit( + cx: &mut compositor::Context, + args: &[&str], + _event: PromptEvent, + ) -> anyhow::Result<()> { + let exit_code = args + .first() + .and_then(|code| code.parse::().ok()) + .unwrap_or(1); + cx.editor.exit_code = exit_code; + + let views: Vec<_> = cx.editor.tree.views().map(|(view, _)| view.id).collect(); + for view_id in views { + cx.editor.close(view_id, false); + } + + Ok(()) + } + fn theme( cx: &mut compositor::Context, args: &[&str], @@ -2411,6 +2430,13 @@ mod cmd { fun: force_quit_all, completer: None, }, + TypableCommand { + name: "cquit", + aliases: &["cq"], + doc: "Quit with exit code (default 1). Accepts an optional integer exit code (:cq 2).", + fun: cquit, + completer: None, + }, TypableCommand { name: "theme", aliases: &[], diff --git a/helix-term/src/main.rs b/helix-term/src/main.rs index 3ae4f7c9..6fa1ce67 100644 --- a/helix-term/src/main.rs +++ b/helix-term/src/main.rs @@ -38,8 +38,13 @@ fn setup_logging(logpath: PathBuf, verbosity: u64) -> Result<()> { Ok(()) } +fn main() -> Result<()> { + let exit_code = main_impl()?; + std::process::exit(exit_code); +} + #[tokio::main] -async fn main() -> Result<()> { +async fn main_impl() -> Result { let cache_dir = helix_core::cache_dir(); if !cache_dir.exists() { std::fs::create_dir_all(&cache_dir).ok(); @@ -109,7 +114,8 @@ FLAGS: // TODO: use the thread local executor to spawn the application task separately from the work pool let mut app = Application::new(args, config).context("unable to create new application")?; - app.run().await.unwrap(); - Ok(()) + let exit_code = app.run().await?; + + Ok(exit_code) } diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 7650d217..e4015707 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -129,6 +129,8 @@ pub struct Editor { pub idle_timer: Pin>, pub last_motion: Option, + + pub exit_code: i32, } #[derive(Debug, Copy, Clone)] @@ -167,6 +169,7 @@ impl Editor { idle_timer: Box::pin(sleep(config.idle_timeout)), last_motion: None, config, + exit_code: 0, } } -- cgit v1.2.3-70-g09d2 From c638b6b60e69697b7e7957ed1af1ac071c41974b Mon Sep 17 00:00:00 2001 From: Cole Helbling Date: Mon, 15 Nov 2021 07:30:45 -0800 Subject: helix-term/commands: implement buffer-close (bc, bclose) (#1035) * helix-view/view: impl method to remove document from jumps * helix-view/editor: impl close_document * helix-view/editor: remove close_buffer argument from `close` According to archseer, this was never implemented or used properly. Now that we have a proper "buffer close" function, we can get rid of this. * helix-term/commands: implement buffer-close (bc, bclose) This behaves the same as Kakoune's `delete-buffer` / `db` command: * With 3 files opened by the user with `:o ab`, `:o cd`, and `:o ef`: * `buffer-close` once closes `ef` and switches to `cd` * `buffer-close` again closes `cd` and switches to `ab` * `buffer-close` again closes `ab` and switches to a scratch buffer * With 3 files opened from the command line with `hx -- ab cd ef`: * `buffer-close` once closes `ab` and switches to `cd` * `buffer-close` again closes `cd` and switches to `ef` * `buffer-close` again closes `ef` and switches to a scratch buffer * With 1 file opened (`ab`): * `buffer-close` once closes `ab` and switches to a scratch buffer * `buffer-close` again closes the scratch buffer and switches to a new scratch buffer * helix-term/commands: implement buffer-close! (bclose!, bc!) Namely, if you have a document open in multiple splits, all the splits will be closed at the same time, leaving only splits without that document focused (or a scratch buffer if they were all focused on that buffer). * helix-view/tree: reset focus if Tree is empty --- helix-term/src/commands.rs | 50 ++++++++++++++++---- helix-view/src/editor.rs | 113 ++++++++++++++++++++++++++++++++++----------- helix-view/src/tree.rs | 3 ++ helix-view/src/view.rs | 4 ++ 4 files changed, 135 insertions(+), 35 deletions(-) (limited to 'helix-view/src/editor.rs') diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 8c0a005c..c7aab726 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -1700,8 +1700,7 @@ mod cmd { buffers_remaining_impl(cx.editor)? } - cx.editor - .close(view!(cx.editor).id, /* close_buffer */ false); + cx.editor.close(view!(cx.editor).id); Ok(()) } @@ -1711,8 +1710,7 @@ mod cmd { _args: &[&str], _event: PromptEvent, ) -> anyhow::Result<()> { - cx.editor - .close(view!(cx.editor).id, /* close_buffer */ false); + cx.editor.close(view!(cx.editor).id); Ok(()) } @@ -1730,6 +1728,28 @@ mod cmd { Ok(()) } + fn buffer_close( + cx: &mut compositor::Context, + _args: &[&str], + _event: PromptEvent, + ) -> anyhow::Result<()> { + let view = view!(cx.editor); + let doc_id = view.doc; + cx.editor.close_document(doc_id, false)?; + Ok(()) + } + + fn force_buffer_close( + cx: &mut compositor::Context, + _args: &[&str], + _event: PromptEvent, + ) -> anyhow::Result<()> { + let view = view!(cx.editor); + let doc_id = view.doc; + cx.editor.close_document(doc_id, true)?; + Ok(()) + } + fn write_impl>( cx: &mut compositor::Context, path: Option

, @@ -1976,7 +1996,7 @@ mod cmd { // close all views let views: Vec<_> = cx.editor.tree.views().map(|(view, _)| view.id).collect(); for view_id in views { - cx.editor.close(view_id, false); + cx.editor.close(view_id); } } @@ -2020,7 +2040,7 @@ mod cmd { // close all views let views: Vec<_> = editor.tree.views().map(|(view, _)| view.id).collect(); for view_id in views { - editor.close(view_id, false); + editor.close(view_id); } Ok(()) @@ -2332,6 +2352,20 @@ mod cmd { fun: open, completer: Some(completers::filename), }, + TypableCommand { + name: "buffer-close", + aliases: &["bc", "bclose"], + doc: "Close the current buffer.", + fun: buffer_close, + completer: None, // FIXME: buffer completer + }, + TypableCommand { + name: "buffer-close!", + aliases: &["bc!", "bclose!"], + doc: "Close the current buffer forcefully (ignoring unsaved changes).", + fun: force_buffer_close, + completer: None, // FIXME: buffer completer + }, TypableCommand { name: "write", aliases: &["w"], @@ -4914,7 +4948,7 @@ fn wclose(cx: &mut Context) { } let view_id = view!(cx.editor).id; // close current split - cx.editor.close(view_id, /* close_buffer */ false); + cx.editor.close(view_id); } fn wonly(cx: &mut Context) { @@ -4926,7 +4960,7 @@ fn wonly(cx: &mut Context) { .collect::>(); for (view_id, focus) in views { if !focus { - cx.editor.close(view_id, /* close_buffer */ false); + cx.editor.close(view_id); } } } diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index e4015707..4712c52a 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -22,7 +22,7 @@ use anyhow::Error; pub use helix_core::diagnostic::Severity; pub use helix_core::register::Registers; use helix_core::syntax; -use helix_core::Position; +use helix_core::{Position, Selection}; use serde::Deserialize; @@ -235,9 +235,28 @@ impl Editor { } } + fn replace_document_in_view(&mut self, current_view: ViewId, doc_id: DocumentId) { + let view = self.tree.get_mut(current_view); + view.doc = doc_id; + view.offset = Position::default(); + + let doc = self.documents.get_mut(&doc_id).unwrap(); + + // initialize selection for view + doc.selections + .entry(view.id) + .or_insert_with(|| Selection::point(0)); + // TODO: reuse align_view + let pos = doc + .selection(view.id) + .primary() + .cursor(doc.text().slice(..)); + let line = doc.text().char_to_line(pos); + view.offset.row = line.saturating_sub(view.inner_area().height as usize / 2); + } + pub fn switch(&mut self, id: DocumentId, action: Action) { use crate::tree::Layout; - use helix_core::Selection; if !self.documents.contains_key(&id) { log::error!("cannot switch to document that does not exist (anymore)"); @@ -271,22 +290,9 @@ impl Editor { view.jumps.push(jump); view.last_accessed_doc = Some(view.doc); } - view.doc = id; - view.offset = Position::default(); - - let (view, doc) = current!(self); - // initialize selection for view - doc.selections - .entry(view.id) - .or_insert_with(|| Selection::point(0)); - // TODO: reuse align_view - let pos = doc - .selection(view.id) - .primary() - .cursor(doc.text().slice(..)); - let line = doc.text().char_to_line(pos); - view.offset.row = line.saturating_sub(view.inner_area().height as usize / 2); + let view_id = view.id; + self.replace_document_in_view(view_id, id); return; } @@ -318,11 +324,16 @@ impl Editor { self._refresh(); } - fn new_file_from_document(&mut self, action: Action, mut document: Document) -> DocumentId { + 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); + id + } + + fn new_file_from_document(&mut self, action: Action, document: Document) -> DocumentId { + let id = self.new_document(document); self.switch(id, action); id } @@ -392,7 +403,7 @@ impl Editor { Ok(id) } - pub fn close(&mut self, id: ViewId, close_buffer: bool) { + pub fn close(&mut self, id: ViewId) { let view = self.tree.get(self.tree.focus); // remove selection self.documents @@ -401,18 +412,66 @@ impl Editor { .selections .remove(&id); - if close_buffer { - // get around borrowck issues - let doc = &self.documents[&view.doc]; + self.tree.remove(id); + self._refresh(); + } - if let Some(language_server) = doc.language_server() { - tokio::spawn(language_server.text_document_did_close(doc.identifier())); - } - self.documents.remove(&view.doc); + 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"), + }; + + if !force && doc.is_modified() { + anyhow::bail!( + "buffer {:?} is modified", + doc.relative_path() + .map(|path| path.to_string_lossy().to_string()) + .unwrap_or_else(|| "[scratch]".into()) + ); + } + + if let Some(language_server) = doc.language_server() { + tokio::spawn(language_server.text_document_did_close(doc.identifier())); + } + + let views_to_close = self + .tree + .views() + .filter_map(|(view, _focus)| { + if view.doc == doc_id { + Some(view.id) + } else { + None + } + }) + .collect::>(); + + for view_id in views_to_close { + self.close(view_id); + } + + 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 + // containing either an existing document, or a brand new document. + if self.tree.views().peekable().peek().is_none() { + let doc_id = self + .documents + .iter() + .map(|(&doc_id, _)| doc_id) + .next() + .unwrap_or_else(|| self.new_document(Document::default())); + let view = View::new(doc_id); + let view_id = self.tree.insert(view); + let doc = self.documents.get_mut(&doc_id).unwrap(); + doc.selections.insert(view_id, Selection::point(0)); } - self.tree.remove(id); self._refresh(); + + Ok(()) } pub fn resize(&mut self, area: Rect) { diff --git a/helix-view/src/tree.rs b/helix-view/src/tree.rs index 064334b1..de5046ac 100644 --- a/helix-view/src/tree.rs +++ b/helix-view/src/tree.rs @@ -314,6 +314,9 @@ impl Tree { pub fn recalculate(&mut self) { if self.is_empty() { + // There are no more views, so the tree should focus itself again. + self.focus = self.root; + return; } diff --git a/helix-view/src/view.rs b/helix-view/src/view.rs index 6a624ded..a77f1562 100644 --- a/helix-view/src/view.rs +++ b/helix-view/src/view.rs @@ -54,6 +54,10 @@ impl JumpList { None } } + + pub fn remove(&mut self, doc_id: &DocumentId) { + self.jumps.retain(|(other_id, _)| other_id != doc_id); + } } #[derive(Debug)] -- cgit v1.2.3-70-g09d2 From 225e7904ec4864b42d0a79f99ebad06ed681c929 Mon Sep 17 00:00:00 2001 From: Cole Helbling Date: Mon, 15 Nov 2021 08:46:39 -0800 Subject: helix-view/editor: use SCRATCH_BUFFER_NAME const (#1104) --- helix-view/src/editor.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'helix-view/src/editor.rs') diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 4712c52a..364865d9 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -1,5 +1,6 @@ use crate::{ clipboard::{get_clipboard_provider, ClipboardProvider}, + document::SCRATCH_BUFFER_NAME, graphics::{CursorKind, Rect}, theme::{self, Theme}, tree::{self, Tree}, @@ -427,7 +428,7 @@ impl Editor { "buffer {:?} is modified", doc.relative_path() .map(|path| path.to_string_lossy().to_string()) - .unwrap_or_else(|| "[scratch]".into()) + .unwrap_or_else(|| SCRATCH_BUFFER_NAME.into()) ); } -- cgit v1.2.3-70-g09d2