From e40e6db2271b9568352b7477ed8b9f9895881cf9 Mon Sep 17 00:00:00 2001 From: Gokul Soumya Date: Thu, 2 Sep 2021 22:05:33 +0530 Subject: feat: Default theme palette using 16 terminal colors --- helix-view/src/graphics.rs | 6 +++--- helix-view/src/theme.rs | 20 +++++++++++++++++++- 2 files changed, 22 insertions(+), 4 deletions(-) (limited to 'helix-view/src') diff --git a/helix-view/src/graphics.rs b/helix-view/src/graphics.rs index 66013ee5..0bfca04a 100644 --- a/helix-view/src/graphics.rs +++ b/helix-view/src/graphics.rs @@ -224,13 +224,13 @@ pub enum Color { Magenta, Cyan, Gray, - DarkGray, LightRed, LightGreen, LightYellow, LightBlue, LightMagenta, LightCyan, + LightGray, White, Rgb(u8, u8, u8), Indexed(u8), @@ -250,14 +250,14 @@ impl From for crossterm::style::Color { Color::Blue => CColor::DarkBlue, Color::Magenta => CColor::DarkMagenta, Color::Cyan => CColor::DarkCyan, - Color::Gray => CColor::Grey, - Color::DarkGray => CColor::DarkGrey, + Color::Gray => CColor::DarkGrey, Color::LightRed => CColor::Red, Color::LightGreen => CColor::Green, Color::LightBlue => CColor::Blue, Color::LightYellow => CColor::Yellow, Color::LightMagenta => CColor::Magenta, Color::LightCyan => CColor::Cyan, + Color::LightGray => CColor::Grey, Color::White => CColor::White, Color::Indexed(i) => CColor::AnsiValue(i), Color::Rgb(r, g, b) => CColor::Rgb { r, g, b }, diff --git a/helix-view/src/theme.rs b/helix-view/src/theme.rs index 74b817d0..9f768505 100644 --- a/helix-view/src/theme.rs +++ b/helix-view/src/theme.rs @@ -5,6 +5,7 @@ use std::{ }; use anyhow::Context; +use helix_core::hashmap; use log::warn; use once_cell::sync::Lazy; use serde::{Deserialize, Deserializer}; @@ -142,7 +143,24 @@ struct ThemePalette { impl Default for ThemePalette { fn default() -> Self { - Self::new(HashMap::new()) + Self::new(hashmap! { + "black".to_string() => Color::Black, + "red".to_string() => Color::Red, + "green".to_string() => Color::Green, + "yellow".to_string() => Color::Yellow, + "blue".to_string() => Color::Blue, + "magenta".to_string() => Color::Magenta, + "cyan".to_string() => Color::Cyan, + "gray".to_string() => Color::Gray, + "light-red".to_string() => Color::LightRed, + "light-green".to_string() => Color::LightGreen, + "light-yellow".to_string() => Color::LightYellow, + "light-blue".to_string() => Color::LightBlue, + "light-magenta".to_string() => Color::LightMagenta, + "light-cyan".to_string() => Color::LightCyan, + "light-gray".to_string() => Color::LightGray, + "white".to_string() => Color::White, + }) } } -- cgit v1.2.3-70-g09d2 From e4e93e176ceca39a834abf7e67bf9bfaa34d886d Mon Sep 17 00:00:00 2001 From: Gokul Soumya Date: Fri, 3 Sep 2021 16:42:29 +0530 Subject: fix: Merge default palette with user palette --- book/src/themes.md | 4 +++- helix-view/src/theme.rs | 45 ++++++++++++++++++++++++++------------------- 2 files changed, 29 insertions(+), 20 deletions(-) (limited to 'helix-view/src') diff --git a/book/src/themes.md b/book/src/themes.md index 8642e659..804baa1c 100644 --- a/book/src/themes.md +++ b/book/src/themes.md @@ -124,7 +124,9 @@ black = "#000000" Remember that the `[palette]` table includes all keys after its header, so you should define the palette after normal theme options. -If there is no `[palette]` section, a default palette which uses the terminal's default 16 colors are used: +The default palette uses the terminal's default 16 colors, and the colors names +are listed below. The `[palette]` section in the config file takes precedence +over it and is merged into the default palette. | Color Name | | --- | diff --git a/helix-view/src/theme.rs b/helix-view/src/theme.rs index 9f768505..9c33685b 100644 --- a/helix-view/src/theme.rs +++ b/helix-view/src/theme.rs @@ -143,30 +143,37 @@ struct ThemePalette { impl Default for ThemePalette { fn default() -> Self { - Self::new(hashmap! { - "black".to_string() => Color::Black, - "red".to_string() => Color::Red, - "green".to_string() => Color::Green, - "yellow".to_string() => Color::Yellow, - "blue".to_string() => Color::Blue, - "magenta".to_string() => Color::Magenta, - "cyan".to_string() => Color::Cyan, - "gray".to_string() => Color::Gray, - "light-red".to_string() => Color::LightRed, - "light-green".to_string() => Color::LightGreen, - "light-yellow".to_string() => Color::LightYellow, - "light-blue".to_string() => Color::LightBlue, - "light-magenta".to_string() => Color::LightMagenta, - "light-cyan".to_string() => Color::LightCyan, - "light-gray".to_string() => Color::LightGray, - "white".to_string() => Color::White, - }) + Self { + palette: hashmap! { + "black".to_string() => Color::Black, + "red".to_string() => Color::Red, + "green".to_string() => Color::Green, + "yellow".to_string() => Color::Yellow, + "blue".to_string() => Color::Blue, + "magenta".to_string() => Color::Magenta, + "cyan".to_string() => Color::Cyan, + "gray".to_string() => Color::Gray, + "light-red".to_string() => Color::LightRed, + "light-green".to_string() => Color::LightGreen, + "light-yellow".to_string() => Color::LightYellow, + "light-blue".to_string() => Color::LightBlue, + "light-magenta".to_string() => Color::LightMagenta, + "light-cyan".to_string() => Color::LightCyan, + "light-gray".to_string() => Color::LightGray, + "white".to_string() => Color::White, + }, + } } } impl ThemePalette { pub fn new(palette: HashMap) -> Self { - Self { palette } + let ThemePalette { + palette: mut default, + } = ThemePalette::default(); + + default.extend(palette); + Self { palette: default } } pub fn hex_string_to_rgb(s: &str) -> Result { -- cgit v1.2.3-70-g09d2 From 99a753a5797d38885560e9026b86952615032556 Mon Sep 17 00:00:00 2001 From: oberblastmeister Date: Sat, 4 Sep 2021 23:42:33 -0400 Subject: Document macros (#693) * add docs * clean up * remove * more * Update helix-view/src/macros.rs Co-authored-by: Ivan Tham Co-authored-by: Ivan Tham --- helix-view/src/editor.rs | 5 ----- helix-view/src/macros.rs | 17 +++++++++++++++++ 2 files changed, 17 insertions(+), 5 deletions(-) (limited to 'helix-view/src') diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index e5ff93ad..562c3c60 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -345,11 +345,6 @@ impl Editor { .find(|doc| doc.path().map(|p| p == path.as_ref()).unwrap_or(false)) } - // pub fn current_document(&self) -> Document { - // let id = self.view().doc; - // let doc = &mut editor.documents[id]; - // } - pub fn cursor(&self) -> (Option, CursorKind) { let view = view!(self); let doc = &self.documents[view.doc]; diff --git a/helix-view/src/macros.rs b/helix-view/src/macros.rs index a06d37e7..c9a04270 100644 --- a/helix-view/src/macros.rs +++ b/helix-view/src/macros.rs @@ -1,3 +1,14 @@ +//! These are macros to make getting very nested fields in the `Editor` struct easier +//! These are macros instead of functions because functions will have to take `&mut self` +//! However, rust doesn't know that you only want a partial borrow instead of borrowing the +//! entire struct which `&mut self` says. This makes it impossible to do other mutable +//! stuff to the struct because it is already borrowed. Because macros are expanded, +//! this circumvents the problem because it is just like indexing fields by hand and then +//! putting a `&mut` in front of it. This way rust can see that we are only borrowing a +//! part of the struct and not the entire thing. + +/// Get the current view and document mutably as a tuple. +/// Returns `(&mut View, &mut Document)` #[macro_export] macro_rules! current { ( $( $editor:ident ).+ ) => {{ @@ -7,6 +18,8 @@ macro_rules! current { }}; } +/// Get the current document mutably. +/// Returns `&mut Document` #[macro_export] macro_rules! doc_mut { ( $( $editor:ident ).+ ) => {{ @@ -14,6 +27,8 @@ macro_rules! doc_mut { }}; } +/// Get the current view mutably. +/// Returns `&mut View` #[macro_export] macro_rules! view_mut { ( $( $editor:ident ).+ ) => {{ @@ -21,6 +36,8 @@ macro_rules! view_mut { }}; } +/// Get the current view immutably +/// Returns `&View` #[macro_export] macro_rules! view { ( $( $editor:ident ).+ ) => {{ -- cgit v1.2.3-70-g09d2 From 57ed5180e0e1af3b3ddcb478c4a6d6ecd969cd40 Mon Sep 17 00:00:00 2001 From: Blaž Hrastnik Date: Mon, 6 Sep 2021 11:00:33 +0900 Subject: lsp: Improve line ending handling when generating TextEdit --- helix-lsp/src/client.rs | 10 +++++++--- helix-view/src/document.rs | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 3 deletions(-) (limited to 'helix-view/src') diff --git a/helix-lsp/src/client.rs b/helix-lsp/src/client.rs index fd34f45d..52b2c1c9 100644 --- a/helix-lsp/src/client.rs +++ b/helix-lsp/src/client.rs @@ -3,7 +3,7 @@ use crate::{ Call, Error, OffsetEncoding, Result, }; -use helix_core::{chars::char_is_line_ending, find_root, ChangeSet, Rope}; +use helix_core::{find_root, ChangeSet, Rope}; use jsonrpc_core as jsonrpc; use lsp_types as lsp; use serde_json::Value; @@ -356,7 +356,6 @@ impl Client { // // Calculation is therefore a bunch trickier. - // TODO: stolen from syntax.rs, share use helix_core::RopeSlice; fn traverse(pos: lsp::Position, text: RopeSlice) -> lsp::Position { let lsp::Position { @@ -366,7 +365,12 @@ impl Client { let mut chars = text.chars().peekable(); while let Some(ch) = chars.next() { - if char_is_line_ending(ch) && !(ch == '\r' && chars.peek() == Some(&'\n')) { + // LSP only considers \n, \r or \r\n as line endings + if ch == '\n' || ch == '\r' { + // consume a \r\n + if chars.peek() == Some(&'\n') { + chars.next(); + } line += 1; character = 0; } else { diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index e890a336..b2c02927 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -891,6 +891,40 @@ impl Default for Document { mod test { use super::*; + #[test] + fn changeset_to_changes_ignore_line_endings() { + use helix_lsp::{lsp, Client, OffsetEncoding}; + let text = Rope::from("hello\r\nworld"); + let mut doc = Document::from(text, None); + let view = ViewId::default(); + doc.set_selection(view, Selection::single(0, 0)); + + let transaction = + Transaction::change(doc.text(), vec![(5, 7, Some("\n".into()))].into_iter()); + let old_doc = doc.text().clone(); + doc.apply(&transaction, view); + let changes = Client::changeset_to_changes( + &old_doc, + doc.text(), + transaction.changes(), + OffsetEncoding::Utf8, + ); + + assert_eq!(doc.text(), "hello\nworld"); + + assert_eq!( + changes, + &[lsp::TextDocumentContentChangeEvent { + range: Some(lsp::Range::new( + lsp::Position::new(0, 5), + lsp::Position::new(1, 0) + )), + text: "\n".into(), + range_length: None, + }] + ); + } + #[test] fn changeset_to_changes() { use helix_lsp::{lsp, Client, OffsetEncoding}; -- cgit v1.2.3-70-g09d2 From c00cf238afe3dbd43327fd74bd8a9ff2dd9c21db Mon Sep 17 00:00:00 2001 From: Blaž Hrastnik Date: Wed, 1 Sep 2021 14:38:47 +0900 Subject: Simplify textDocument/didClose, we don't need to look up LSP again --- helix-view/src/editor.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) (limited to 'helix-view/src') diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 562c3c60..050f2645 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -287,14 +287,9 @@ impl Editor { if close_buffer { // get around borrowck issues - let language_servers = &mut self.language_servers; let doc = &self.documents[view.doc]; - let language_server = doc - .language - .as_ref() - .and_then(|language| language_servers.get(language).ok()); - if let Some(language_server) = language_server { + if let Some(language_server) = doc.language_server() { tokio::spawn(language_server.text_document_did_close(doc.identifier())); } self.documents.remove(view.doc); -- cgit v1.2.3-70-g09d2 From 184637c55acca49380372ca118f13b3390bcb003 Mon Sep 17 00:00:00 2001 From: Blaž Hrastnik Date: Wed, 1 Sep 2021 14:49:48 +0900 Subject: lsp: refactor format so we stop cloning the language_server --- helix-lsp/src/client.rs | 14 +++++++++----- helix-view/src/document.rs | 23 +++++++++++++---------- 2 files changed, 22 insertions(+), 15 deletions(-) (limited to 'helix-view/src') diff --git a/helix-lsp/src/client.rs b/helix-lsp/src/client.rs index ac6ae70a..fdff553f 100644 --- a/helix-lsp/src/client.rs +++ b/helix-lsp/src/client.rs @@ -583,19 +583,19 @@ impl Client { // formatting - pub async fn text_document_formatting( + pub fn text_document_formatting( &self, text_document: lsp::TextDocumentIdentifier, options: lsp::FormattingOptions, work_done_token: Option, - ) -> anyhow::Result> { + ) -> Option>>> { let capabilities = self.capabilities.get().unwrap(); // check if we're able to format match capabilities.document_formatting_provider { Some(lsp::OneOf::Left(true)) | Some(lsp::OneOf::Right(_)) => (), // None | Some(false) - _ => return Ok(Vec::new()), + _ => return None, }; // TODO: return err::unavailable so we can fall back to tree sitter formatting @@ -605,9 +605,13 @@ impl Client { work_done_progress_params: lsp::WorkDoneProgressParams { work_done_token }, }; - let response = self.request::(params).await?; + let request = self.call::(params); - Ok(response.unwrap_or_default()) + Some(async move { + let json = request.await?; + let response: Vec = serde_json::from_value(json)?; + Ok(response) + }) } pub async fn text_document_range_formatting( diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index b2c02927..a27be8e6 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -386,21 +386,24 @@ impl Document { /// If supported, returns the changes that should be applied to this document in order /// to format it nicely. pub fn format(&self) -> Option + 'static> { - if let Some(language_server) = self.language_server.clone() { + if let Some(language_server) = self.language_server() { let text = self.text.clone(); - let id = self.identifier(); + let offset_encoding = language_server.offset_encoding(); + let request = language_server.text_document_formatting( + self.identifier(), + lsp::FormattingOptions::default(), + None, + )?; + let fut = async move { - let edits = language_server - .text_document_formatting(id, lsp::FormattingOptions::default(), None) - .await - .unwrap_or_else(|e| { - log::warn!("LSP formatting failed: {}", e); - Default::default() - }); + let edits = request.await.unwrap_or_else(|e| { + log::warn!("LSP formatting failed: {}", e); + Default::default() + }); LspFormatting { doc: text, edits, - offset_encoding: language_server.offset_encoding(), + offset_encoding, } }; Some(fut) -- cgit v1.2.3-70-g09d2 From 800d79b584cd09020488b8a614e5214b929d8f5d Mon Sep 17 00:00:00 2001 From: Blaž Hrastnik Date: Wed, 1 Sep 2021 14:54:11 +0900 Subject: ls: Refactor textDocument/didSave in a similar vein --- helix-lsp/src/client.rs | 19 ++++++++++--------- helix-view/src/document.rs | 8 ++++---- 2 files changed, 14 insertions(+), 13 deletions(-) (limited to 'helix-view/src') diff --git a/helix-lsp/src/client.rs b/helix-lsp/src/client.rs index fdff553f..b8fbfddb 100644 --- a/helix-lsp/src/client.rs +++ b/helix-lsp/src/client.rs @@ -490,11 +490,11 @@ impl Client { // will_save / will_save_wait_until - pub async fn text_document_did_save( + pub fn text_document_did_save( &self, text_document: lsp::TextDocumentIdentifier, text: &Rope, - ) -> Result<()> { + ) -> Option>> { let capabilities = self.capabilities.get().unwrap(); let include_text = match &capabilities.text_document_sync { @@ -507,17 +507,18 @@ impl Client { include_text, }) => include_text.unwrap_or(false), // Supported(false) - _ => return Ok(()), + _ => return None, }, // unsupported - _ => return Ok(()), + _ => return None, }; - self.notify::(lsp::DidSaveTextDocumentParams { - text_document, - text: include_text.then(|| text.into()), - }) - .await + Some(self.notify::( + lsp::DidSaveTextDocumentParams { + text_document, + text: include_text.then(|| text.into()), + }, + )) } pub fn completion( diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index a27be8e6..5677eb44 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -471,10 +471,10 @@ impl Document { let mut file = File::create(path).await?; to_writer(&mut file, encoding, &text).await?; - if let Some(language_server) = language_server { - language_server - .text_document_did_save(identifier, &text) - .await?; + if let Some(notification) = language_server.and_then(|language_server| { + language_server.text_document_did_save(identifier, &text) + }) { + notification.await?; } Ok(()) -- cgit v1.2.3-70-g09d2 From 10b690b5bd5d3e9ee477782ebfe3f6ff8d11cb3f Mon Sep 17 00:00:00 2001 From: Blaž Hrastnik Date: Thu, 2 Sep 2021 10:49:23 +0900 Subject: Drop some &mut bounds where & would have sufficed --- helix-term/src/job.rs | 4 ++-- helix-view/src/document.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'helix-view/src') diff --git a/helix-term/src/job.rs b/helix-term/src/job.rs index 2ac41926..4fa38174 100644 --- a/helix-term/src/job.rs +++ b/helix-term/src/job.rs @@ -61,7 +61,7 @@ impl Jobs { } pub fn handle_callback( - &mut self, + &self, editor: &mut Editor, compositor: &mut Compositor, call: anyhow::Result>, @@ -84,7 +84,7 @@ impl Jobs { } } - pub fn add(&mut self, j: Job) { + pub fn add(&self, j: Job) { if j.wait { self.wait_futures.push(j.future); } else { diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index 5677eb44..71f6680c 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -649,7 +649,7 @@ impl Document { // } // emit lsp notification - if let Some(language_server) = &self.language_server { + if let Some(language_server) = self.language_server() { let notify = language_server.text_document_did_change( self.versioned_identifier(), &old_doc, -- cgit v1.2.3-70-g09d2 From dc7799b980826ffe33ed635968def79daf20bd10 Mon Sep 17 00:00:00 2001 From: Blaž Hrastnik Date: Thu, 2 Sep 2021 11:28:40 +0900 Subject: lsp: Refactor code that could use document_by_path_mut --- helix-term/src/application.rs | 11 +++-------- helix-view/src/editor.rs | 5 +++++ 2 files changed, 8 insertions(+), 8 deletions(-) (limited to 'helix-view/src') diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 8241ce3a..d3b65a4f 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -276,15 +276,10 @@ impl Application { match notification { Notification::PublishDiagnostics(params) => { - let path = Some(params.uri.to_file_path().unwrap()); + let path = params.uri.to_file_path().unwrap(); + let doc = self.editor.document_by_path_mut(&path); - let doc = self - .editor - .documents - .iter_mut() - .find(|(_, doc)| doc.path() == path.as_ref()); - - if let Some((_, doc)) = doc { + if let Some(doc) = doc { let text = doc.text(); let diagnostics = params diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 050f2645..0d914e45 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -340,6 +340,11 @@ impl Editor { .find(|doc| doc.path().map(|p| p == path.as_ref()).unwrap_or(false)) } + pub fn document_by_path_mut>(&mut self, path: P) -> Option<&mut Document> { + self.documents_mut() + .find(|doc| doc.path().map(|p| p == path.as_ref()).unwrap_or(false)) + } + pub fn cursor(&self) -> (Option, CursorKind) { let view = view!(self); let doc = &self.documents[view.doc]; -- cgit v1.2.3-70-g09d2 From 59ed1c8c78ab996273bcc910c11724bc8402f711 Mon Sep 17 00:00:00 2001 From: Blaž Hrastnik Date: Thu, 2 Sep 2021 12:52:32 +0900 Subject: Simplify documents & documents_mut() --- helix-view/src/editor.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'helix-view/src') diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 0d914e45..c8abd5b5 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -319,20 +319,24 @@ impl Editor { view.ensure_cursor_in_view(doc, self.config.scrolloff) } + #[inline] pub fn document(&self, id: DocumentId) -> Option<&Document> { self.documents.get(id) } + #[inline] pub fn document_mut(&mut self, id: DocumentId) -> Option<&mut Document> { self.documents.get_mut(id) } + #[inline] pub fn documents(&self) -> impl Iterator { - self.documents.iter().map(|(_id, doc)| doc) + self.documents.values() } + #[inline] pub fn documents_mut(&mut self) -> impl Iterator { - self.documents.iter_mut().map(|(_id, doc)| doc) + self.documents.values_mut() } pub fn document_by_path>(&self, path: P) -> Option<&Document> { -- cgit v1.2.3-70-g09d2 From 46f3c69f06cc55f36bcc6244a9f96c2481836dea Mon Sep 17 00:00:00 2001 From: Blaž Hrastnik Date: Thu, 2 Sep 2021 13:55:08 +0900 Subject: lsp: Don't send notifications until initialize completes Then send open events for all documents with the LSP attached. --- helix-lsp/src/lib.rs | 98 +++++++++++++++++++++---------------------- helix-lsp/src/transport.rs | 29 ++++++++++++- helix-term/src/application.rs | 31 ++++++++++++++ helix-view/src/editor.rs | 5 ++- 4 files changed, 111 insertions(+), 52 deletions(-) (limited to 'helix-view/src') diff --git a/helix-lsp/src/lib.rs b/helix-lsp/src/lib.rs index e10c107b..7357c885 100644 --- a/helix-lsp/src/lib.rs +++ b/helix-lsp/src/lib.rs @@ -226,6 +226,8 @@ impl MethodCall { #[derive(Debug, PartialEq, Clone)] pub enum Notification { + // we inject this notification to signal the LSP is ready + Initialized, PublishDiagnostics(lsp::PublishDiagnosticsParams), ShowMessage(lsp::ShowMessageParams), LogMessage(lsp::LogMessageParams), @@ -237,6 +239,7 @@ impl Notification { use lsp::notification::Notification as _; let notification = match method { + lsp::notification::Initialized::METHOD => Self::Initialized, lsp::notification::PublishDiagnostics::METHOD => { let params: lsp::PublishDiagnosticsParams = params .parse() @@ -294,7 +297,7 @@ impl Registry { } } - pub fn get_by_id(&mut self, id: usize) -> Option<&Client> { + pub fn get_by_id(&self, id: usize) -> Option<&Client> { self.inner .values() .find(|(client_id, _)| client_id == &id) @@ -302,55 +305,52 @@ impl Registry { } pub fn get(&mut self, language_config: &LanguageConfiguration) -> Result> { - if let Some(config) = &language_config.language_server { - // avoid borrow issues - let inner = &mut self.inner; - let s_incoming = &mut self.incoming; - - match inner.entry(language_config.scope.clone()) { - Entry::Occupied(entry) => Ok(entry.get().1.clone()), - Entry::Vacant(entry) => { - // initialize a new client - let id = self.counter.fetch_add(1, Ordering::Relaxed); - let (client, incoming, initialize_notify) = Client::start( - &config.command, - &config.args, - serde_json::from_str(language_config.config.as_deref().unwrap_or("")).ok(), - id, - )?; - s_incoming.push(UnboundedReceiverStream::new(incoming)); - let client = Arc::new(client); - - let _client = client.clone(); - // Initialize the client asynchronously - tokio::spawn(async move { - use futures_util::TryFutureExt; - let value = _client - .capabilities - .get_or_try_init(|| { - _client - .initialize() - .map_ok(|response| response.capabilities) - }) - .await; - - value.expect("failed to initialize capabilities"); - - // next up, notify - _client - .notify::(lsp::InitializedParams {}) - .await - .unwrap(); - - initialize_notify.notify_one(); - }); - - entry.insert((id, client.clone())); - Ok(client) - } + let config = match &language_config.language_server { + Some(config) => config, + None => return Err(Error::LspNotDefined), + }; + + match self.inner.entry(language_config.scope.clone()) { + Entry::Occupied(entry) => Ok(entry.get().1.clone()), + Entry::Vacant(entry) => { + // initialize a new client + let id = self.counter.fetch_add(1, Ordering::Relaxed); + let (client, incoming, initialize_notify) = Client::start( + &config.command, + &config.args, + serde_json::from_str(language_config.config.as_deref().unwrap_or("")).ok(), + id, + )?; + self.incoming.push(UnboundedReceiverStream::new(incoming)); + let client = Arc::new(client); + + // Initialize the client asynchronously + let _client = client.clone(); + tokio::spawn(async move { + use futures_util::TryFutureExt; + let value = _client + .capabilities + .get_or_try_init(|| { + _client + .initialize() + .map_ok(|response| response.capabilities) + }) + .await; + + value.expect("failed to initialize capabilities"); + + // next up, notify + _client + .notify::(lsp::InitializedParams {}) + .await + .unwrap(); + + initialize_notify.notify_one(); + }); + + entry.insert((id, client.clone())); + Ok(client) } - } else { - Err(Error::LspNotDefined) } } diff --git a/helix-lsp/src/transport.rs b/helix-lsp/src/transport.rs index 071c5b93..cf7e66a8 100644 --- a/helix-lsp/src/transport.rs +++ b/helix-lsp/src/transport.rs @@ -64,11 +64,16 @@ impl Transport { let transport = Arc::new(transport); - tokio::spawn(Self::recv(transport.clone(), server_stdout, client_tx)); + tokio::spawn(Self::recv( + transport.clone(), + server_stdout, + client_tx.clone(), + )); tokio::spawn(Self::err(transport.clone(), server_stderr)); tokio::spawn(Self::send( transport, server_stdin, + client_tx, client_rx, notify.clone(), )); @@ -269,6 +274,7 @@ impl Transport { async fn send( transport: Arc, mut server_stdin: BufWriter, + mut client_tx: UnboundedSender<(usize, jsonrpc::Call)>, mut client_rx: UnboundedReceiver, initialize_notify: Arc, ) { @@ -303,6 +309,22 @@ impl Transport { _ = initialize_notify.notified() => { // TODO: notified is technically not cancellation safe // server successfully initialized is_pending = false; + + use lsp_types::notification::Notification; + // Hack: inject an initialized notification so we trigger code that needs to happen after init + let notification = ServerMessage::Call(jsonrpc::Call::Notification(jsonrpc::Notification { + jsonrpc: None, + + method: lsp_types::notification::Initialized::METHOD.to_string(), + params: jsonrpc::Params::None, + })); + match transport.process_server_message(&mut client_tx, notification).await { + Ok(_) => {} + Err(err) => { + error!("err: <- {:?}", err); + } + } + // drain the pending queue and send payloads to server for msg in pending_messages.drain(..) { log::info!("Draining pending message {:?}", msg); @@ -317,6 +339,11 @@ impl Transport { msg = client_rx.recv() => { if let Some(msg) = msg { if is_pending && !is_initialize(&msg) { + // ignore notifications + if let Payload::Notification(_) = msg { + continue; + } + log::info!("Language server not initialized, delaying request"); pending_messages.push(msg); } else { diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index d3b65a4f..e21c5504 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -275,6 +275,37 @@ impl Application { }; match notification { + Notification::Initialized => { + let language_server = + match self.editor.language_servers.get_by_id(server_id) { + Some(language_server) => language_server, + None => { + warn!("can't find language server with id `{}`", server_id); + return; + } + }; + + let docs = self.editor.documents().filter(|doc| { + doc.language_server().map(|server| server.id()) == Some(server_id) + }); + + // trigger textDocument/didOpen for docs that are already open + for doc in docs { + // TODO: extract and share with editor.open + let language_id = doc + .language() + .and_then(|s| s.split('.').last()) // source.rust + .map(ToOwned::to_owned) + .unwrap_or_default(); + + tokio::spawn(language_server.text_document_did_open( + doc.url().unwrap(), + doc.version(), + doc.text(), + language_id, + )); + } + } Notification::PublishDiagnostics(params) => { let path = params.uri.to_file_path().unwrap(); let doc = self.editor.document_by_path_mut(&path); diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index c8abd5b5..3d2d4a87 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -255,20 +255,21 @@ impl Editor { .and_then(|language| self.language_servers.get(language).ok()); if let Some(language_server) = language_server { - doc.set_language_server(Some(language_server.clone())); - let language_id = doc .language() .and_then(|s| s.split('.').last()) // source.rust .map(ToOwned::to_owned) .unwrap_or_default(); + // TODO: this now races with on_init code if the init happens too quickly tokio::spawn(language_server.text_document_did_open( doc.url().unwrap(), doc.version(), doc.text(), language_id, )); + + doc.set_language_server(Some(language_server)); } let id = self.documents.insert(doc); -- cgit v1.2.3-70-g09d2 From 37606bad47fe0e197cb34fc7d52856597c32ce33 Mon Sep 17 00:00:00 2001 From: Blaž Hrastnik Date: Thu, 2 Sep 2021 13:55:55 +0900 Subject: lsp: doc.language_server() is None until initialize completes --- helix-lsp/src/client.rs | 4 ++++ helix-view/src/document.rs | 13 +++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) (limited to 'helix-view/src') diff --git a/helix-lsp/src/client.rs b/helix-lsp/src/client.rs index 27e4697c..f2bb0059 100644 --- a/helix-lsp/src/client.rs +++ b/helix-lsp/src/client.rs @@ -93,6 +93,10 @@ impl Client { } } + pub fn is_initialized(&self) -> bool { + self.capabilities.get().is_some() + } + pub fn capabilities(&self) -> &lsp::ServerCapabilities { self.capabilities .get() diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index 71f6680c..362a433e 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -798,9 +798,18 @@ impl Document { self.version } - #[inline] pub fn language_server(&self) -> Option<&helix_lsp::Client> { - self.language_server.as_deref() + let server = self.language_server.as_deref(); + let initialized = server + .map(|server| server.is_initialized()) + .unwrap_or(false); + + // only resolve language_server if it's initialized + if initialized { + server + } else { + None + } } #[inline] -- cgit v1.2.3-70-g09d2 From 64099af3f1f5957de8f4203c665063d02ba4bac9 Mon Sep 17 00:00:00 2001 From: Blaž Hrastnik Date: Thu, 2 Sep 2021 16:07:21 +0900 Subject: Don't panic on save if language_server isn't initialized --- helix-view/src/document.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) (limited to 'helix-view/src') diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index 362a433e..6de60995 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -471,10 +471,15 @@ impl Document { let mut file = File::create(path).await?; to_writer(&mut file, encoding, &text).await?; - if let Some(notification) = language_server.and_then(|language_server| { - language_server.text_document_did_save(identifier, &text) - }) { - notification.await?; + if let Some(language_server) = language_server { + if language_server.is_initialized() { + return Ok(()); + } + if let Some(notification) = + language_server.text_document_did_save(identifier, &text) + { + notification.await?; + } } Ok(()) -- cgit v1.2.3-70-g09d2 From 72cf86e4626c01c6ce2371c7b134ab7a7041620c Mon Sep 17 00:00:00 2001 From: Blaž Hrastnik Date: Wed, 8 Sep 2021 14:52:09 +0900 Subject: Regex prompts should have a history with a specifiable register --- helix-term/src/commands.rs | 38 ++++++++++++++-------------- helix-term/src/ui/editor.rs | 4 +-- helix-term/src/ui/mod.rs | 9 +++---- helix-view/src/editor.rs | 6 ++--- helix-view/src/lib.rs | 2 -- helix-view/src/register_selection.rs | 48 ------------------------------------ 6 files changed, 29 insertions(+), 78 deletions(-) delete mode 100644 helix-view/src/register_selection.rs (limited to 'helix-view/src') diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 38e65537..841af22a 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -44,7 +44,7 @@ use once_cell::sync::Lazy; use serde::de::{self, Deserialize, Deserializer}; pub struct Context<'a> { - pub selected_register: helix_view::RegisterSelection, + pub register: Option, pub count: Option, pub editor: &'a mut Editor, @@ -1030,7 +1030,8 @@ fn select_all(cx: &mut Context) { } fn select_regex(cx: &mut Context) { - let prompt = ui::regex_prompt(cx, "select:".into(), move |view, doc, _, regex| { + let reg = cx.register.unwrap_or('/'); + let prompt = ui::regex_prompt(cx, "select:".into(), Some(reg), move |view, doc, regex| { let text = doc.text().slice(..); if let Some(selection) = selection::select_on_matches(text, doc.selection(view.id), ®ex) { @@ -1042,7 +1043,8 @@ fn select_regex(cx: &mut Context) { } fn split_selection(cx: &mut Context) { - let prompt = ui::regex_prompt(cx, "split:".into(), move |view, doc, _, regex| { + let reg = cx.register.unwrap_or('/'); + let prompt = ui::regex_prompt(cx, "split:".into(), Some(reg), move |view, doc, regex| { let text = doc.text().slice(..); let selection = selection::split_on_matches(text, doc.selection(view.id), ®ex); doc.set_selection(view.id, selection); @@ -1100,6 +1102,7 @@ fn search_impl(doc: &mut Document, view: &mut View, contents: &str, regex: &Rege // TODO: use one function for search vs extend fn search(cx: &mut Context) { + let reg = cx.register.unwrap_or('/'); let (_, doc) = current!(cx.editor); // TODO: could probably share with select_on_matches? @@ -1108,10 +1111,8 @@ fn search(cx: &mut Context) { // feed chunks into the regex yet let contents = doc.text().slice(..).to_string(); - let prompt = ui::regex_prompt(cx, "search:".into(), move |view, doc, registers, regex| { + let prompt = ui::regex_prompt(cx, "search:".into(), Some(reg), move |view, doc, regex| { search_impl(doc, view, &contents, ®ex, false); - // TODO: only store on enter (accept), not update - registers.write('/', vec![regex.as_str().to_string()]); }); cx.push_layer(Box::new(prompt)); @@ -1119,9 +1120,9 @@ fn search(cx: &mut Context) { fn search_next_impl(cx: &mut Context, extend: bool) { let (view, doc) = current!(cx.editor); - let registers = &mut cx.editor.registers; + let registers = &cx.editor.registers; if let Some(query) = registers.read('/') { - let query = query.first().unwrap(); + let query = query.last().unwrap(); let contents = doc.text().slice(..).to_string(); let regex = Regex::new(query).unwrap(); search_impl(doc, view, &contents, ®ex, extend); @@ -1141,7 +1142,7 @@ fn search_selection(cx: &mut Context) { let contents = doc.text().slice(..); let query = doc.selection(view.id).primary().fragment(contents); let regex = regex::escape(&query); - cx.editor.registers.write('/', vec![regex]); + cx.editor.registers.get_mut('/').push(regex); search_next(cx); } @@ -1200,7 +1201,7 @@ fn delete_selection_impl(reg: &mut Register, doc: &mut Document, view_id: ViewId } fn delete_selection(cx: &mut Context) { - let reg_name = cx.selected_register.name(); + let reg_name = cx.register.unwrap_or('"'); let (view, doc) = current!(cx.editor); let registers = &mut cx.editor.registers; let reg = registers.get_mut(reg_name); @@ -1213,7 +1214,7 @@ fn delete_selection(cx: &mut Context) { } fn change_selection(cx: &mut Context) { - let reg_name = cx.selected_register.name(); + let reg_name = cx.register.unwrap_or('"'); let (view, doc) = current!(cx.editor); let registers = &mut cx.editor.registers; let reg = registers.get_mut(reg_name); @@ -3346,12 +3347,12 @@ fn yank(cx: &mut Context) { let msg = format!( "yanked {} selection(s) to register {}", values.len(), - cx.selected_register.name() + cx.register.unwrap_or('"') ); cx.editor .registers - .write(cx.selected_register.name(), values); + .write(cx.register.unwrap_or('"'), values); cx.editor.set_status(msg); exit_select_mode(cx); @@ -3524,7 +3525,7 @@ fn paste_primary_clipboard_before(cx: &mut Context) { } fn replace_with_yanked(cx: &mut Context) { - let reg_name = cx.selected_register.name(); + let reg_name = cx.register.unwrap_or('"'); let (view, doc) = current!(cx.editor); let registers = &mut cx.editor.registers; @@ -3575,7 +3576,7 @@ fn replace_selections_with_primary_clipboard(cx: &mut Context) { } fn paste_after(cx: &mut Context) { - let reg_name = cx.selected_register.name(); + let reg_name = cx.register.unwrap_or('"'); let (view, doc) = current!(cx.editor); let registers = &mut cx.editor.registers; @@ -3589,7 +3590,7 @@ fn paste_after(cx: &mut Context) { } fn paste_before(cx: &mut Context) { - let reg_name = cx.selected_register.name(); + let reg_name = cx.register.unwrap_or('"'); let (view, doc) = current!(cx.editor); let registers = &mut cx.editor.registers; @@ -3770,7 +3771,8 @@ fn join_selections(cx: &mut Context) { fn keep_selections(cx: &mut Context) { // keep selections matching regex - let prompt = ui::regex_prompt(cx, "keep:".into(), move |view, doc, _, regex| { + let reg = cx.register.unwrap_or('/'); + let prompt = ui::regex_prompt(cx, "keep:".into(), Some(reg), move |view, doc, regex| { let text = doc.text().slice(..); if let Some(selection) = selection::keep_matches(text, doc.selection(view.id), ®ex) { @@ -4103,7 +4105,7 @@ fn wclose(cx: &mut Context) { fn select_register(cx: &mut Context) { cx.on_next_key(move |cx, event| { if let Some(ch) = event.char() { - cx.editor.selected_register.select(ch); + cx.editor.selected_register = Some(ch); } }) } diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index e8cd40cf..52cf3d2b 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -710,7 +710,7 @@ impl EditorView { // debug_assert!(cxt.count != 0); // set the register - cxt.selected_register = cxt.editor.selected_register.take(); + cxt.register = cxt.editor.selected_register.take(); self.handle_keymap_event(mode, cxt, event); if self.keymaps.pending().is_empty() { @@ -887,9 +887,9 @@ impl EditorView { impl Component for EditorView { fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult { let mut cxt = commands::Context { - selected_register: helix_view::RegisterSelection::default(), editor: &mut cx.editor, count: None, + register: None, callback: None, on_next_key_callback: None, jobs: cx.jobs, diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs index 0a1e24b5..07eef352 100644 --- a/helix-term/src/ui/mod.rs +++ b/helix-term/src/ui/mod.rs @@ -20,7 +20,6 @@ pub use spinner::{ProgressSpinners, Spinner}; pub use text::Text; use helix_core::regex::Regex; -use helix_core::register::Registers; use helix_view::{Document, Editor, View}; use std::path::PathBuf; @@ -28,7 +27,8 @@ use std::path::PathBuf; pub fn regex_prompt( cx: &mut crate::commands::Context, prompt: std::borrow::Cow<'static, str>, - fun: impl Fn(&mut View, &mut Document, &mut Registers, Regex) + 'static, + history_register: Option, + fun: impl Fn(&mut View, &mut Document, Regex) + 'static, ) -> Prompt { let (view, doc) = current!(cx.editor); let view_id = view.id; @@ -36,7 +36,7 @@ pub fn regex_prompt( Prompt::new( prompt, - None, + history_register, |_input: &str| Vec::new(), // this is fine because Vec::new() doesn't allocate move |cx: &mut crate::compositor::Context, input: &str, event: PromptEvent| { match event { @@ -56,12 +56,11 @@ pub fn regex_prompt( match Regex::new(input) { Ok(regex) => { let (view, doc) = current!(cx.editor); - let registers = &mut cx.editor.registers; // revert state to what it was before the last update doc.set_selection(view.id, snapshot.clone()); - fun(view, doc, registers, regex); + fun(view, doc, regex); view.ensure_cursor_in_view(doc, cx.editor.config.scrolloff); } diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 3d2d4a87..52a0060c 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -3,7 +3,7 @@ use crate::{ graphics::{CursorKind, Rect}, theme::{self, Theme}, tree::Tree, - Document, DocumentId, RegisterSelection, View, ViewId, + Document, DocumentId, View, ViewId, }; use futures_util::future; @@ -73,7 +73,7 @@ pub struct Editor { pub tree: Tree, pub documents: SlotMap, pub count: Option, - pub selected_register: RegisterSelection, + pub selected_register: Option, pub registers: Registers, pub theme: Theme, pub language_servers: helix_lsp::Registry, @@ -111,7 +111,7 @@ impl Editor { tree: Tree::new(area), documents: SlotMap::with_key(), count: None, - selected_register: RegisterSelection::default(), + selected_register: None, theme: themes.default(), language_servers, syn_loader: config_loader, diff --git a/helix-view/src/lib.rs b/helix-view/src/lib.rs index 9bcc0b7d..c37474d6 100644 --- a/helix-view/src/lib.rs +++ b/helix-view/src/lib.rs @@ -8,7 +8,6 @@ pub mod graphics; pub mod info; pub mod input; pub mod keyboard; -pub mod register_selection; pub mod theme; pub mod tree; pub mod view; @@ -20,6 +19,5 @@ slotmap::new_key_type! { pub use document::Document; pub use editor::Editor; -pub use register_selection::RegisterSelection; pub use theme::Theme; pub use view::View; diff --git a/helix-view/src/register_selection.rs b/helix-view/src/register_selection.rs deleted file mode 100644 index a2b78f72..00000000 --- a/helix-view/src/register_selection.rs +++ /dev/null @@ -1,48 +0,0 @@ -/// Register selection and configuration -/// -/// This is a kind a of specialized `Option` for register selection. -/// Point is to keep whether the register selection has been explicitely -/// set or not while being convenient by knowing the default register name. -#[derive(Debug)] -pub struct RegisterSelection { - selected: char, - default_name: char, -} - -impl RegisterSelection { - pub fn new(default_name: char) -> Self { - Self { - selected: default_name, - default_name, - } - } - - pub fn select(&mut self, name: char) { - self.selected = name; - } - - pub fn take(&mut self) -> Self { - Self { - selected: std::mem::replace(&mut self.selected, self.default_name), - default_name: self.default_name, - } - } - - pub fn is_default(&self) -> bool { - self.selected == self.default_name - } - - pub fn name(&self) -> char { - self.selected - } -} - -impl Default for RegisterSelection { - fn default() -> Self { - let default_name = '"'; - Self { - selected: default_name, - default_name, - } - } -} -- cgit v1.2.3-70-g09d2 From 0b1bc566e4e3cfa88e23133fb018c998f2052c52 Mon Sep 17 00:00:00 2001 From: Blaž Hrastnik Date: Thu, 9 Sep 2021 11:54:01 +0900 Subject: fix: lsp: Regression with textDocument/didSave not getting sent --- helix-view/src/document.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'helix-view/src') diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index 6de60995..1f1b1f5f 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -472,7 +472,7 @@ impl Document { to_writer(&mut file, encoding, &text).await?; if let Some(language_server) = language_server { - if language_server.is_initialized() { + if !language_server.is_initialized() { return Ok(()); } if let Some(notification) = -- cgit v1.2.3-70-g09d2 From ef532e0c0df3e9f8bf4ac5af74b54f32b7ea2728 Mon Sep 17 00:00:00 2001 From: Kirawi Date: Wed, 15 Sep 2021 01:58:06 -0400 Subject: log errors produced when trying to initialize the LSP (#746) --- helix-lsp/src/lib.rs | 10 +++++++++- helix-view/src/editor.rs | 12 ++++++++---- 2 files changed, 17 insertions(+), 5 deletions(-) (limited to 'helix-view/src') diff --git a/helix-lsp/src/lib.rs b/helix-lsp/src/lib.rs index 7357c885..35cff754 100644 --- a/helix-lsp/src/lib.rs +++ b/helix-lsp/src/lib.rs @@ -318,7 +318,15 @@ impl Registry { let (client, incoming, initialize_notify) = Client::start( &config.command, &config.args, - serde_json::from_str(language_config.config.as_deref().unwrap_or("")).ok(), + serde_json::from_str(language_config.config.as_deref().unwrap_or("")) + .map_err(|e| { + log::error!( + "LSP Config, {}, in `languages.toml` for `{}`", + e, + language_config.scope() + ) + }) + .ok(), id, )?; self.incoming.push(UnboundedReceiverStream::new(incoming)); diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 52a0060c..a3d0d032 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -249,10 +249,14 @@ impl Editor { let mut doc = Document::open(&path, None, Some(&self.theme), Some(&self.syn_loader))?; // 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).ok()); + let language_server = doc.language.as_ref().and_then(|language| { + self.language_servers + .get(language) + .map_err(|e| { + log::error!("Failed to get LSP, {}, for `{}`", e, language.scope()) + }) + .ok() + }); if let Some(language_server) = language_server { let language_id = doc -- cgit v1.2.3-70-g09d2 From 4a003782a51a94259ef3b5ddfacb2a148c5056e7 Mon Sep 17 00:00:00 2001 From: kraem Date: Mon, 20 Sep 2021 06:45:07 +0200 Subject: enable smart case regex search by default (#761) --- TODO.md | 2 -- book/src/configuration.md | 1 + book/src/keymap.md | 3 +-- helix-term/src/commands.rs | 12 ++++++++++-- helix-term/src/ui/mod.rs | 12 +++++++++++- helix-view/src/editor.rs | 3 +++ 6 files changed, 26 insertions(+), 7 deletions(-) (limited to 'helix-view/src') diff --git a/TODO.md b/TODO.md index d81cf302..90e7e450 100644 --- a/TODO.md +++ b/TODO.md @@ -22,8 +22,6 @@ as you type completion! - [ ] lsp: signature help -- [ ] search: smart case by default: insensitive unless upper detected - 2 - [ ] macro recording - [ ] extend selection (treesitter select parent node) (replaces viw, vi(, va( etc ) diff --git a/book/src/configuration.md b/book/src/configuration.md index 5a28362d..90cdd8a7 100644 --- a/book/src/configuration.md +++ b/book/src/configuration.md @@ -17,6 +17,7 @@ To override global configuration parameters, create a `config.toml` file located | `scroll-lines` | Number of lines to scroll per scroll wheel step. | `3` | | `shell` | Shell to use when running external commands. | Unix: `["sh", "-c"]`
Windows: `["cmd", "/C"]` | | `line-number` | Line number display (`absolute`, `relative`) | `absolute` | +| `smart-case` | Enable smart case regex searching (case insensitive unless pattern contains upper case characters) | `true` | ## LSP diff --git a/book/src/keymap.md b/book/src/keymap.md index 4fa5033d..16d2420d 100644 --- a/book/src/keymap.md +++ b/book/src/keymap.md @@ -104,8 +104,7 @@ ### Search -> TODO: The search implementation isn't ideal yet -- we don't support searching -in reverse, or searching via smartcase. +> TODO: The search implementation isn't ideal yet -- we don't support searching in reverse. | Key | Description | Command | | ----- | ----------- | ------- | diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 703b92d1..d40bb9cf 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -5,7 +5,7 @@ use helix_core::{ match_brackets, movement::{self, Direction}, object, pos_at_coords, - regex::{self, Regex}, + regex::{self, Regex, RegexBuilder}, register::Register, search, selection, surround, textobject, LineEnding, Position, Range, Rope, RopeGraphemes, RopeSlice, Selection, SmallVec, Tendril, Transaction, @@ -1154,7 +1154,15 @@ fn search_next_impl(cx: &mut Context, extend: bool) { if let Some(query) = registers.read('/') { let query = query.last().unwrap(); let contents = doc.text().slice(..).to_string(); - if let Ok(regex) = Regex::new(query) { + let case_insensitive = if cx.editor.config.smart_case { + !query.chars().any(char::is_uppercase) + } else { + false + }; + if let Ok(regex) = RegexBuilder::new(query) + .case_insensitive(case_insensitive) + .build() + { search_impl(doc, view, &contents, ®ex, extend); } else { // get around warning `mutable_borrow_reservation_conflict` diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs index 07eef352..f6536eb2 100644 --- a/helix-term/src/ui/mod.rs +++ b/helix-term/src/ui/mod.rs @@ -20,6 +20,7 @@ pub use spinner::{ProgressSpinners, Spinner}; pub use text::Text; use helix_core::regex::Regex; +use helix_core::regex::RegexBuilder; use helix_view::{Document, Editor, View}; use std::path::PathBuf; @@ -53,7 +54,16 @@ pub fn regex_prompt( return; } - match Regex::new(input) { + let case_insensitive = if cx.editor.config.smart_case { + !input.chars().any(char::is_uppercase) + } else { + false + }; + + match RegexBuilder::new(input) + .case_insensitive(case_insensitive) + .build() + { Ok(regex) => { let (view, doc) = current!(cx.editor); diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index a3d0d032..b7df4a9b 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -39,6 +39,8 @@ pub struct Config { pub line_number: LineNumber, /// Middle click paste support. Defaults to true pub middle_click_paste: bool, + /// Smart case: Case insensitive searching unless pattern contains upper case characters. Defaults to true. + pub smart_case: bool, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] @@ -64,6 +66,7 @@ impl Default for Config { }, line_number: LineNumber::Absolute, middle_click_paste: true, + smart_case: true, } } } -- cgit v1.2.3-70-g09d2 From a958d34bfbcf45c01ce0d9c0d76e681fb863fc6a Mon Sep 17 00:00:00 2001 From: lurpahi Date: Thu, 23 Sep 2021 18:28:44 -0700 Subject: Add option for automatic insertion of closing-parens/brackets/etc (#779) * Add auto-pair editor option * Document auto-pair editor option * Make cargo fmt happy * Actually make cargo fmt happy * Rename auto-pair option to auto-pairs * Inline a few constants Co-authored-by: miaomai --- book/src/configuration.md | 1 + helix-term/src/commands.rs | 11 +++++++---- helix-view/src/editor.rs | 3 +++ 3 files changed, 11 insertions(+), 4 deletions(-) (limited to 'helix-view/src') diff --git a/book/src/configuration.md b/book/src/configuration.md index 90cdd8a7..60b12bfd 100644 --- a/book/src/configuration.md +++ b/book/src/configuration.md @@ -18,6 +18,7 @@ To override global configuration parameters, create a `config.toml` file located | `shell` | Shell to use when running external commands. | Unix: `["sh", "-c"]`
Windows: `["cmd", "/C"]` | | `line-number` | Line number display (`absolute`, `relative`) | `absolute` | | `smart-case` | Enable smart case regex searching (case insensitive unless pattern contains upper case characters) | `true` | +| `auto-pairs` | Enable automatic insertion of pairs to parenthese, brackets, etc. | `true` | ## LSP diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index ac93b5d0..117ba046 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -3371,17 +3371,20 @@ pub mod insert { } use helix_core::auto_pairs; - const HOOKS: &[Hook] = &[auto_pairs::hook, insert]; - const POST_HOOKS: &[PostHook] = &[completion, signature_help]; pub fn insert_char(cx: &mut Context, c: char) { let (view, doc) = current!(cx.editor); + let hooks: &[Hook] = match cx.editor.config.auto_pairs { + true => &[auto_pairs::hook, insert], + false => &[insert], + }; + let text = doc.text(); let selection = doc.selection(view.id).clone().cursors(text.slice(..)); // run through insert hooks, stopping on the first one that returns Some(t) - for hook in HOOKS { + for hook in hooks { if let Some(transaction) = hook(text, &selection, c) { doc.apply(&transaction, view.id); break; @@ -3391,7 +3394,7 @@ pub mod insert { // TODO: need a post insert hook too for certain triggers (autocomplete, signature help, etc) // this could also generically look at Transaction, but it's a bit annoying to look at // Operation instead of Change. - for hook in POST_HOOKS { + for hook in &[completion, signature_help] { hook(cx, c); } } diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index b7df4a9b..b08a2df2 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -41,6 +41,8 @@ pub struct Config { pub middle_click_paste: bool, /// Smart case: Case insensitive searching unless pattern contains upper case characters. Defaults to true. pub smart_case: bool, + /// Automatic insertion of pairs to parentheses, brackets, etc. Defaults to true. + pub auto_pairs: bool, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] @@ -67,6 +69,7 @@ impl Default for Config { line_number: LineNumber::Absolute, middle_click_paste: true, smart_case: true, + auto_pairs: true, } } } -- cgit v1.2.3-70-g09d2