From 0a7add4ad4b0f9838e585b12d8d07f8ed637eb97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Wed, 9 Jun 2021 15:46:13 +0900 Subject: [PATCH 01/76] Only recalculate resize during rendering, this stops flashing on resize --- helix-term/src/ui/editor.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index ffe755e6065f..3fcd69210b7a 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -698,9 +698,8 @@ impl Component for EditorView { fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult { match event { Event::Resize(width, height) => { - // HAXX: offset the render area height by 1 to account for prompt/commandline - cx.editor - .resize(Rect::new(0, 0, width, height.saturating_sub(1))); + // Ignore this event, we handle resizing just before rendering to screen. + // Handling it here but not re-rendering will cause flashing EventResult::Consumed(None) } Event::Key(key) => { From a4564adadd1f77850b92d772e58c6457e14ecc3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Tue, 10 Aug 2021 10:52:02 +0900 Subject: [PATCH 02/76] fix: Don't crash if language servers time out --- helix-term/src/application.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 9cd9ee7e4fd9..3d59c33a7333 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -527,7 +527,9 @@ impl Application { self.event_loop().await; - self.editor.close_language_servers(None).await?; + if self.editor.close_language_servers(None).await.is_err() { + log::error!("Timed out waiting for language servers to shutdown"); + }; self.restore_term()?; From 21e5662125774c5f16b70be1173c673a4fdb1176 Mon Sep 17 00:00:00 2001 From: Omnikar Date: Mon, 9 Aug 2021 21:57:07 -0400 Subject: [PATCH 03/76] Make `exit_select_mode` check current mode (#568) Change `exit_select_mode` to check that the current mode is select mode before switching to normal mode --- helix-term/src/commands.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 29be5ccd0f1f..f8c5d4804058 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -2560,7 +2560,10 @@ fn select_mode(cx: &mut Context) { } fn exit_select_mode(cx: &mut Context) { - doc_mut!(cx.editor).mode = Mode::Normal; + let doc = doc_mut!(cx.editor); + if doc.mode == Mode::Select { + doc.mode = Mode::Normal; + } } fn goto_impl( From 86209c93a3569c9c152bd8b1c66798a78a1b9317 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Tue, 10 Aug 2021 10:58:37 +0900 Subject: [PATCH 04/76] Appease clippy --- helix-term/src/ui/editor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 3fcd69210b7a..487a1ce368fc 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -697,7 +697,7 @@ impl EditorView { impl Component for EditorView { fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult { match event { - Event::Resize(width, height) => { + Event::Resize(_width, _height) => { // Ignore this event, we handle resizing just before rendering to screen. // Handling it here but not re-rendering will cause flashing EventResult::Consumed(None) From b239f0f45ffd0993cffc2a676651a0770b705fb5 Mon Sep 17 00:00:00 2001 From: Kirawi <67773714+kirawi@users.noreply.github.com> Date: Tue, 10 Aug 2021 01:09:57 -0400 Subject: [PATCH 05/76] add java highlighting (#448) --- languages.toml | 8 ++ runtime/queries/java/highlights.scm | 129 ++++++++++++++++++++++++++++ 2 files changed, 137 insertions(+) create mode 100644 runtime/queries/java/highlights.scm diff --git a/languages.toml b/languages.toml index 8cd092e4844b..15d78a1af341 100644 --- a/languages.toml +++ b/languages.toml @@ -197,6 +197,14 @@ comment-token = "#" language-server = { command = "julia", args = [ "--startup-file=no", "--history-file=no", "-e", "using LanguageServer;using Pkg;import StaticLint;import SymbolServer;env_path = dirname(Pkg.Types.Context().env.project_file);server = LanguageServer.LanguageServerInstance(stdin, stdout, env_path, \"\");server.runlinter = true;run(server);" ] } indent = { tab-width = 2, unit = " " } +[[language]] +name = "java" +scope = "source.java" +injection-regex = "java" +file-types = ["java"] +roots = [] +indent = { tab-width = 4, unit = " " } + # [[language]] # name = "haskell" # scope = "source.haskell" diff --git a/runtime/queries/java/highlights.scm b/runtime/queries/java/highlights.scm new file mode 100644 index 000000000000..3f8ae0d50fcb --- /dev/null +++ b/runtime/queries/java/highlights.scm @@ -0,0 +1,129 @@ +; Methods + +(method_declaration + name: (identifier) @function.method) +(method_invocation + name: (identifier) @function.method) +(super) @function.builtin + +; Annotations + +(annotation + name: (identifier) @attribute) +(marker_annotation + name: (identifier) @attribute) + +"@" @operator + +; Types + +(interface_declaration + name: (identifier) @type) +(class_declaration + name: (identifier) @type) +(enum_declaration + name: (identifier) @type) + +((field_access + object: (identifier) @type) + (#match? @type "^[A-Z]")) +((scoped_identifier + scope: (identifier) @type) + (#match? @type "^[A-Z]")) + +(constructor_declaration + name: (identifier) @type) + +(type_identifier) @type + +[ + (boolean_type) + (integral_type) + (floating_point_type) + (floating_point_type) + (void_type) +] @type.builtin + +; Variables + +((identifier) @constant + (#match? @constant "^_*[A-Z][A-Z\d_]+")) + +(identifier) @variable + +(this) @variable.builtin + +; Literals + +[ + (hex_integer_literal) + (decimal_integer_literal) + (octal_integer_literal) + (decimal_floating_point_literal) + (hex_floating_point_literal) +] @number + +[ + (character_literal) + (string_literal) +] @string + +[ + (true) + (false) + (null_literal) +] @constant.builtin + +(comment) @comment + +; Keywords + +[ + "abstract" + "assert" + "break" + "case" + "catch" + "class" + "continue" + "default" + "do" + "else" + "enum" + "exports" + "extends" + "final" + "finally" + "for" + "if" + "implements" + "import" + "instanceof" + "interface" + "module" + "native" + "new" + "open" + "opens" + "package" + "private" + "protected" + "provides" + "public" + "requires" + "return" + "static" + "strictfp" + "switch" + "synchronized" + "throw" + "throws" + "to" + "transient" + "transitive" + "try" + "uses" + "volatile" + "while" + "with" +] @keyword From 27b551d34593cd94a3fba016c3a7f2042f9d9d38 Mon Sep 17 00:00:00 2001 From: Dmitry Sharshakov Date: Tue, 10 Aug 2021 08:35:20 +0300 Subject: [PATCH 06/76] helix-term: handle scrolling when mouse is enabled (#554) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * helix-term: handle scrolling when mouse is enabled Signed-off-by: Dmitry Sharshakov * helix-term: configure scrolling speed Signed-off-by: Dmitry Sharshakov * helix-term: use new config for scrolling Signed-off-by: Dmitry Sharshakov * config: defaults for edtior config Signed-off-by: Dmitry Sharshakov * config: add scroll-lines property Signed-off-by: Dmitry Sharshakov * helix-term: scroll hovered view Signed-off-by: Dmitry Sharshakov * helix-term: support inverted scrolling Signed-off-by: Dmitry Sharshakov * helix-term: remove duplicating code Signed-off-by: Dmitry Sharshakov * helix-term: do not focus view while scrolled Signed-off-by: Dmitry Sharshakov * helix-term: refactor mouse events and scrolling Signed-off-by: Dmitry Sharshakov * simplify Co-authored-by: Blaž Hrastnik --- helix-term/src/commands.rs | 2 +- helix-term/src/ui/editor.rs | 176 +++++++++++++++++++++++------------- helix-view/src/editor.rs | 5 +- 3 files changed, 116 insertions(+), 67 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index f8c5d4804058..b918256ec8c8 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -871,7 +871,7 @@ fn switch_to_lowercase(cx: &mut Context) { doc.append_changes_to_history(view.id); } -fn scroll(cx: &mut Context, offset: usize, direction: Direction) { +pub fn scroll(cx: &mut Context, offset: usize, direction: Direction) { use Direction::*; let (view, doc) = current!(cx.editor); let cursor = coords_at_pos( diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 487a1ce368fc..aa21a3890112 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -9,6 +9,7 @@ use crate::{ use helix_core::{ coords_at_pos, graphemes::{ensure_grapheme_boundary_next, next_grapheme_boundary, prev_grapheme_boundary}, + movement::Direction, syntax::{self, HighlightEvent}, unicode::segmentation::UnicodeSegmentation, unicode::width::UnicodeWidthStr, @@ -694,8 +695,112 @@ impl EditorView { } } +impl EditorView { + fn handle_mouse_event( + &mut self, + event: MouseEvent, + cxt: &mut commands::Context, + ) -> EventResult { + match event { + MouseEvent { + kind: MouseEventKind::Down(MouseButton::Left), + row, + column, + modifiers, + .. + } => { + let editor = &mut cxt.editor; + + let result = editor.tree.views().find_map(|(view, _focus)| { + view.pos_at_screen_coords(&editor.documents[view.doc], row, column) + .map(|pos| (pos, view.id)) + }); + + if let Some((pos, view_id)) = result { + let doc = &mut editor.documents[editor.tree.get(view_id).doc]; + + if modifiers == crossterm::event::KeyModifiers::ALT { + let selection = doc.selection(view_id).clone(); + doc.set_selection(view_id, selection.push(Range::point(pos))); + } else { + doc.set_selection(view_id, Selection::point(pos)); + } + + editor.tree.focus = view_id; + + return EventResult::Consumed(None); + } + + EventResult::Ignored + } + + MouseEvent { + kind: MouseEventKind::Drag(MouseButton::Left), + row, + column, + .. + } => { + let (view, doc) = current!(cxt.editor); + + let pos = match view.pos_at_screen_coords(doc, row, column) { + Some(pos) => pos, + None => return EventResult::Ignored, + }; + + let mut selection = doc.selection(view.id).clone(); + let primary = selection.primary_mut(); + *primary = Range::new(primary.anchor, pos); + doc.set_selection(view.id, selection); + EventResult::Consumed(None) + } + + MouseEvent { + kind: MouseEventKind::ScrollUp | MouseEventKind::ScrollDown, + row, + column, + .. + } => { + let current_view = cxt.editor.tree.focus; + + let direction = match event.kind { + MouseEventKind::ScrollUp => Direction::Backward, + MouseEventKind::ScrollDown => Direction::Forward, + _ => unreachable!(), + }; + + let result = cxt.editor.tree.views().find_map(|(view, _focus)| { + view.pos_at_screen_coords(&cxt.editor.documents[view.doc], row, column) + .map(|_| view.id) + }); + + match result { + Some(view_id) => cxt.editor.tree.focus = view_id, + None => return EventResult::Ignored, + } + + let offset = cxt.editor.config.scroll_lines.abs() as usize; + commands::scroll(cxt, offset, direction); + + cxt.editor.tree.focus = current_view; + + EventResult::Consumed(None) + } + _ => EventResult::Ignored, + } + } +} + 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, + callback: None, + on_next_key_callback: None, + jobs: cx.jobs, + }; + match event { Event::Resize(_width, _height) => { // Ignore this event, we handle resizing just before rendering to screen. @@ -706,20 +811,11 @@ impl Component for EditorView { let mut key = KeyEvent::from(key); canonicalize_key(&mut key); // clear status - cx.editor.status_msg = None; + cxt.editor.status_msg = None; - let (_, doc) = current!(cx.editor); + let (_, doc) = current!(cxt.editor); let mode = doc.mode(); - let mut cxt = commands::Context { - selected_register: helix_view::RegisterSelection::default(), - editor: &mut cx.editor, - count: None, - callback: None, - on_next_key_callback: None, - jobs: cx.jobs, - }; - if let Some(on_next_key) = self.on_next_key.take() { // if there's a command waiting input, do that first on_next_key(&mut cxt, key); @@ -773,12 +869,12 @@ impl Component for EditorView { // if the command consumed the last view, skip the render. // on the next loop cycle the Application will then terminate. - if cx.editor.should_close() { + if cxt.editor.should_close() { return EventResult::Ignored; } - let (view, doc) = current!(cx.editor); - view.ensure_cursor_in_view(doc, cx.editor.config.scrolloff); + let (view, doc) = current!(cxt.editor); + view.ensure_cursor_in_view(doc, cxt.editor.config.scrolloff); // mode transitions match (mode, doc.mode()) { @@ -805,58 +901,8 @@ impl Component for EditorView { EventResult::Consumed(callback) } - Event::Mouse(MouseEvent { - kind: MouseEventKind::Down(MouseButton::Left), - row, - column, - modifiers, - .. - }) => { - let editor = &mut cx.editor; - - let result = editor.tree.views().find_map(|(view, _focus)| { - view.pos_at_screen_coords(&editor.documents[view.doc], row, column) - .map(|pos| (pos, view.id)) - }); - - if let Some((pos, view_id)) = result { - let doc = &mut editor.documents[editor.tree.get(view_id).doc]; - - if modifiers == crossterm::event::KeyModifiers::ALT { - let selection = doc.selection(view_id).clone(); - doc.set_selection(view_id, selection.push(Range::point(pos))); - } else { - doc.set_selection(view_id, Selection::point(pos)); - } - - editor.tree.focus = view_id; - - return EventResult::Consumed(None); - } - - EventResult::Ignored - } - Event::Mouse(MouseEvent { - kind: MouseEventKind::Drag(MouseButton::Left), - row, - column, - .. - }) => { - let (view, doc) = current!(cx.editor); - - let pos = match view.pos_at_screen_coords(doc, row, column) { - Some(pos) => pos, - None => return EventResult::Ignored, - }; - - let mut selection = doc.selection(view.id).clone(); - let primary = selection.primary_mut(); - *primary = Range::new(primary.anchor, pos); - doc.set_selection(view.id, selection); - EventResult::Consumed(None) - } - Event::Mouse(_) => EventResult::Ignored, + Event::Mouse(event) => self.handle_mouse_event(event, &mut cxt), } } diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index e5ba0d512fdc..ec3cedd605bd 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -21,10 +21,12 @@ use helix_core::Position; use serde::Deserialize; #[derive(Debug, Clone, PartialEq, Deserialize)] -#[serde(rename_all = "kebab-case")] +#[serde(rename_all = "kebab-case", default)] pub struct Config { /// Padding to keep between the edge of the screen and the cursor when scrolling. Defaults to 5. pub scrolloff: usize, + /// Number of lines to scroll at once. Defaults to 3 + pub scroll_lines: isize, /// Mouse support. Defaults to true. pub mouse: bool, } @@ -33,6 +35,7 @@ impl Default for Config { fn default() -> Self { Self { scrolloff: 5, + scroll_lines: 3, mouse: true, } } From dde2be93956f82622c85171bd03dab1b6864318c Mon Sep 17 00:00:00 2001 From: Nathan Vegdahl Date: Tue, 10 Aug 2021 17:17:59 -0700 Subject: [PATCH 07/76] Fix surround_replace replacing the wrong character on the right. (#571) Fixes #569. --- helix-term/src/commands.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index b918256ec8c8..e15332c84884 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -4017,11 +4017,11 @@ fn surround_replace(cx: &mut Context) { let transaction = Transaction::change( doc.text(), change_pos.iter().enumerate().map(|(i, &pos)| { - if i % 2 == 0 { - (pos, pos + 1, Some(Tendril::from_char(open))) - } else { - (pos.saturating_sub(1), pos, Some(Tendril::from_char(close))) - } + ( + pos, + pos + 1, + Some(Tendril::from_char(if i % 2 == 0 { open } else { close })), + ) }), ); doc.apply(&transaction, view.id); From f917b5a441ff3ae582358b6939ffbf889f4aa530 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Tue, 10 Aug 2021 11:19:07 +0900 Subject: [PATCH 08/76] ui: completion: Use sort_text to sort the completions --- helix-term/src/ui/completion.rs | 4 ++++ helix-term/src/ui/menu.rs | 27 +++++++++++---------------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/helix-term/src/ui/completion.rs b/helix-term/src/ui/completion.rs index 2725d53debd6..f359e7ec4a12 100644 --- a/helix-term/src/ui/completion.rs +++ b/helix-term/src/ui/completion.rs @@ -14,6 +14,10 @@ use helix_lsp::{lsp, util}; use lsp::CompletionItem; impl menu::Item for CompletionItem { + fn sort_text(&self) -> &str { + self.filter_text.as_ref().unwrap_or(&self.label).as_str() + } + fn filter_text(&self) -> &str { self.filter_text.as_ref().unwrap_or(&self.label).as_str() } diff --git a/helix-term/src/ui/menu.rs b/helix-term/src/ui/menu.rs index 1e1c5427282d..26eff1d8e5ee 100644 --- a/helix-term/src/ui/menu.rs +++ b/helix-term/src/ui/menu.rs @@ -11,7 +11,7 @@ use helix_view::{graphics::Rect, Editor}; use tui::layout::Constraint; pub trait Item { - // TODO: sort_text + fn sort_text(&self) -> &str; fn filter_text(&self) -> &str; fn label(&self) -> &str; @@ -64,24 +64,21 @@ impl Menu { let Self { ref mut matcher, ref mut matches, + ref options, .. } = *self; // reuse the matches allocation matches.clear(); - matches.extend( - self.options - .iter() - .enumerate() - .filter_map(|(index, option)| { - let text = option.filter_text(); - // TODO: using fuzzy_indices could give us the char idx for match highlighting - matcher - .fuzzy_match(text, pattern) - .map(|score| (index, score)) - }), - ); - matches.sort_unstable_by_key(|(_, score)| -score); + matches.extend(options.iter().enumerate().filter_map(|(index, option)| { + let text = option.filter_text(); + // TODO: using fuzzy_indices could give us the char idx for match highlighting + matcher + .fuzzy_match(text, pattern) + .map(|score| (index, score)) + })); + // matches.sort_unstable_by_key(|(_, score)| -score); + matches.sort_unstable_by_key(|(index, _score)| options[*index].sort_text()); // reset cursor position self.cursor = None; @@ -223,8 +220,6 @@ impl Component for Menu { EventResult::Ignored } - // TODO: completion sorting - fn required_size(&mut self, viewport: (u16, u16)) -> Option<(u16, u16)> { let n = self .options From 6cd77ef380e1f87f5af613dbf63acac6b7fec47d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Tue, 10 Aug 2021 11:20:05 +0900 Subject: [PATCH 09/76] nix: Update flake --- flake.lock | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/flake.lock b/flake.lock index 4aec1594da6d..054a51544321 100644 --- a/flake.lock +++ b/flake.lock @@ -40,11 +40,11 @@ "rustOverlay": "rustOverlay" }, "locked": { - "lastModified": 1627940369, - "narHash": "sha256-KtY837WKsX9B/pIKFDKzN0wl1t3et1JZjMjGa7SAZxI=", + "lastModified": 1628489367, + "narHash": "sha256-ADYKHf8aPo1qTw1J+eqVprnEbH8lES0yZamD/yM7RAM=", "owner": "yusdacra", "repo": "nix-cargo-integration", - "rev": "fac8518469e226db4805ff80788979c847b0c322", + "rev": "0dc8383aae5f791a48e34120edb04670b947dc0b", "type": "github" }, "original": { @@ -55,11 +55,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1627391865, - "narHash": "sha256-tPoWBO9Nzu3wuX37WcnctzL6LoDCErJLnfLGqqmXCm4=", + "lastModified": 1628465643, + "narHash": "sha256-QSNw9bDq9uGUniQQtakRuw4m21Jxugm23SXLVgEV4DM=", "owner": "nixos", "repo": "nixpkgs", - "rev": "8ecc61c91a596df7d3293603a9c2384190c1b89a", + "rev": "6ef4f522d63f22b40004319778761040d3197390", "type": "github" }, "original": { @@ -79,11 +79,11 @@ "rustOverlay": { "flake": false, "locked": { - "lastModified": 1627870491, - "narHash": "sha256-0Myg04QOIcTN1RhgfRNx0i/iCRyVyf/Z6rJxZUmot5k=", + "lastModified": 1628475192, + "narHash": "sha256-A32shcfPMCll7psCS0OBxVCkA+PKfeWvmU4y9lgNZzU=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "71d825269cfaa30605d058bd92381be9af87b0be", + "rev": "56a8ddb827cbe7a914be88f4a52998a5f93ff468", "type": "github" }, "original": { From 627b89931576f7af86166ae8d5cbc55537877473 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Tue, 10 Aug 2021 14:12:57 +0900 Subject: [PATCH 10/76] ui: completion: Insert suggestions when tabbing over them Fixes #498 --- helix-term/src/ui/completion.rs | 81 +++++++++++++++++++++++---------- 1 file changed, 56 insertions(+), 25 deletions(-) diff --git a/helix-term/src/ui/completion.rs b/helix-term/src/ui/completion.rs index f359e7ec4a12..6d7376528df1 100644 --- a/helix-term/src/ui/completion.rs +++ b/helix-term/src/ui/completion.rs @@ -5,7 +5,7 @@ use tui::buffer::Buffer as Surface; use std::borrow::Cow; use helix_core::Transaction; -use helix_view::{graphics::Rect, Editor}; +use helix_view::{graphics::Rect, Document, Editor, View}; use crate::commands; use crate::ui::{menu, Markdown, Menu, Popup, PromptEvent}; @@ -81,15 +81,47 @@ impl Completion { ) -> Self { // let items: Vec = Vec::new(); let menu = Menu::new(items, move |editor: &mut Editor, item, event| { + fn item_to_transaction( + doc: &Document, + view: &View, + item: &CompletionItem, + offset_encoding: helix_lsp::OffsetEncoding, + ) -> Transaction { + if let Some(edit) = &item.text_edit { + let edit = match edit { + lsp::CompletionTextEdit::Edit(edit) => edit.clone(), + lsp::CompletionTextEdit::InsertAndReplace(item) => { + unimplemented!("completion: insert_and_replace {:?}", item) + } + }; + util::generate_transaction_from_edits( + doc.text(), + vec![edit], + offset_encoding, // TODO: should probably transcode in Client + ) + } else { + let text = item.insert_text.as_ref().unwrap_or(&item.label); + let cursor = doc + .selection(view.id) + .primary() + .cursor(doc.text().slice(..)); + Transaction::change( + doc.text(), + vec![(cursor, cursor, Some(text.as_str().into()))].into_iter(), + ) + } + } + match event { PromptEvent::Abort => {} - PromptEvent::Validate => { + PromptEvent::Update => { let (view, doc) = current!(editor); // always present here let item = item.unwrap(); // if more text was entered, remove it + // TODO: ideally to undo we should keep the last completion tx revert, and map it over new changes let cursor = doc .selection(view.id) .primary() @@ -102,30 +134,30 @@ impl Completion { doc.apply(&remove, view.id); } - let transaction = if let Some(edit) = &item.text_edit { - let edit = match edit { - lsp::CompletionTextEdit::Edit(edit) => edit.clone(), - lsp::CompletionTextEdit::InsertAndReplace(item) => { - unimplemented!("completion: insert_and_replace {:?}", item) - } - }; - util::generate_transaction_from_edits( - doc.text(), - vec![edit], - offset_encoding, // TODO: should probably transcode in Client - ) - } else { - let text = item.insert_text.as_ref().unwrap_or(&item.label); - let cursor = doc - .selection(view.id) - .primary() - .cursor(doc.text().slice(..)); - Transaction::change( + let transaction = item_to_transaction(doc, view, item, offset_encoding); + doc.apply(&transaction, view.id); + } + PromptEvent::Validate => { + let (view, doc) = current!(editor); + + // always present here + let item = item.unwrap(); + + // if more text was entered, remove it + // TODO: ideally to undo we should keep the last completion tx revert, and map it over new changes + let cursor = doc + .selection(view.id) + .primary() + .cursor(doc.text().slice(..)); + if trigger_offset < cursor { + let remove = Transaction::change( doc.text(), - vec![(cursor, cursor, Some(text.as_str().into()))].into_iter(), - ) - }; + vec![(trigger_offset, cursor, None)].into_iter(), + ); + doc.apply(&remove, view.id); + } + let transaction = item_to_transaction(doc, view, item, offset_encoding); doc.apply(&transaction, view.id); if let Some(additional_edits) = &item.additional_text_edits { @@ -140,7 +172,6 @@ impl Completion { } } } - _ => (), }; }); let popup = Popup::new(menu); From 7b16ae7c4a4c84ac86e078a13eac443b512d534a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Wed, 11 Aug 2021 13:23:48 +0900 Subject: [PATCH 11/76] Bump deps --- Cargo.lock | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5a5fcf4b4486..c5b54272b59c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -313,6 +313,7 @@ dependencies = [ "arc-swap", "etcetera", "helix-syntax", + "log", "once_cell", "quickcheck", "regex", @@ -495,9 +496,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.98" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320cfe77175da3a483efed4bc0adc1968ca050b098ce4f2f1c13a56626128790" +checksum = "a7f823d141fe0a24df1e23b4af4e3c7ba9e5966ec514ea068c93024aa7deb765" [[package]] name = "libloading" @@ -675,9 +676,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "proc-macro2" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038" +checksum = "5c7ed8b8c7b886ea3ed7dde405212185f423ab44682667c8c6dd14aa1d9f6612" dependencies = [ "unicode-xid", ] @@ -731,9 +732,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ab49abadf3f9e1c4bc499e8845e152ad87d2ad2d30371841171169e9d75feee" +checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" dependencies = [ "bitflags", ] @@ -887,9 +888,9 @@ checksum = "1ad1d488a557b235fc46dae55512ffbfc429d2482b08b4d9435ab07384ca8aec" [[package]] name = "slab" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f173ac3d1a7e3b28003f40de0b5ce7fe2710f9b9dc3fc38664cebee46b3b6527" +checksum = "c307a32c1c5c437f38c7fd45d753050587732ba8628319fbdf12a7e289ccc590" [[package]] name = "slotmap" @@ -914,9 +915,9 @@ checksum = "d44a3643b4ff9caf57abcee9c2c621d6c03d9135e0d8b589bd9afb5992cb176a" [[package]] name = "syn" -version = "1.0.73" +version = "1.0.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f71489ff30030d2ae598524f61326b902466f72a0fb1a8564c001cc63425bcc7" +checksum = "1873d832550d4588c3dbc20f01361ab00bfe741048f71e3fecf145a7cc18b29c" dependencies = [ "proc-macro2", "quote", @@ -974,9 +975,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ac2e1d4bd0f75279cfd5a076e0d578bbf02c22b7c39e766c437dd49b3ec43e0" +checksum = "848a1e1181b9f6753b5e96a092749e29b11d19ede67dfbbd6c7dc7e0f49b5338" dependencies = [ "tinyvec_macros", ] From 55f1f04717f4f1a657c1f68123af245a7d069eef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Wed, 11 Aug 2021 13:23:57 +0900 Subject: [PATCH 12/76] Highlight (html) tags --- theme.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/theme.toml b/theme.toml index 67b7dc57b13f..4df026672a38 100644 --- a/theme.toml +++ b/theme.toml @@ -17,6 +17,7 @@ constructor = "lilac" function = "white" "function.macro" = "lilac" "function.builtin" = "white" +tag = "almond" comment = "sirocco" constant = "white" "constant.builtin" = "white" From 6d52424303bf92a9abcfe8daa45cff145966f820 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Wed, 11 Aug 2021 13:53:38 +0900 Subject: [PATCH 13/76] fix: Adjust scroll offset/padding calculation to prevent wobble Fixes #324 --- helix-term/src/commands.rs | 16 +++++++++++----- helix-view/src/view.rs | 5 ++++- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index e15332c84884..7994c26ec24c 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -110,10 +110,12 @@ fn align_view(doc: &Document, view: &mut View, align: Align) { .cursor(doc.text().slice(..)); let line = doc.text().char_to_line(pos); + let height = view.area.height.saturating_sub(1) as usize; // -1 for statusline + let relative = match align { - Align::Center => view.area.height as usize / 2, + Align::Center => height / 2, Align::Top => 0, - Align::Bottom => view.area.height as usize, + Align::Bottom => height, }; view.first_line = line.saturating_sub(relative); @@ -448,17 +450,21 @@ fn goto_first_nonwhitespace(cx: &mut Context) { fn goto_window(cx: &mut Context, align: Align) { let (view, doc) = current!(cx.editor); + let height = view.area.height.saturating_sub(1) as usize; // -1 for statusline + // - 1 so we have at least one gap in the middle. + // a height of 6 with padding of 3 on each side will keep shifting the view back and forth + // as we type let scrolloff = cx .editor .config .scrolloff - .min(view.area.height as usize / 2); // TODO: user pref + .min(height.saturating_sub(1) / 2); let last_line = view.last_line(doc); let line = match align { Align::Top => (view.first_line + scrolloff), - Align::Center => (view.first_line + (view.area.height as usize / 2)), + Align::Center => (view.first_line + (height / 2)), Align::Bottom => last_line.saturating_sub(scrolloff), } .min(last_line.saturating_sub(scrolloff)); @@ -894,7 +900,7 @@ pub fn scroll(cx: &mut Context, offset: usize, direction: Direction) { .editor .config .scrolloff - .min(view.area.height as usize / 2); // TODO: user pref + .min(view.area.height as usize / 2); view.first_line = match direction { Forward => view.first_line + offset, diff --git a/helix-view/src/view.rs b/helix-view/src/view.rs index 25efbde53653..265f7df80bbf 100644 --- a/helix-view/src/view.rs +++ b/helix-view/src/view.rs @@ -93,7 +93,10 @@ impl View { let height = self.area.height.saturating_sub(1); // - 1 for statusline let last_line = (self.first_line + height as usize).saturating_sub(1); - let scrolloff = scrolloff.min(self.area.height as usize / 2); + // - 1 so we have at least one gap in the middle. + // a height of 6 with padding of 3 on each side will keep shifting the view back and forth + // as we type + let scrolloff = scrolloff.min(height.saturating_sub(1) as usize / 2); // TODO: not ideal const OFFSET: usize = 7; // 1 diagnostic + 5 linenr + 1 gutter From 25a8a475c53a90fed9ad40db7c327a3cdf481cd7 Mon Sep 17 00:00:00 2001 From: Gokul Soumya Date: Thu, 12 Aug 2021 06:30:19 +0530 Subject: [PATCH 14/76] Refactor theme parsing (#570) --- helix-view/src/graphics.rs | 24 +++- helix-view/src/theme.rs | 247 ++++++++++++++++++------------------- 2 files changed, 144 insertions(+), 127 deletions(-) diff --git a/helix-view/src/graphics.rs b/helix-view/src/graphics.rs index ed530533a190..9a7a86c3a359 100644 --- a/helix-view/src/graphics.rs +++ b/helix-view/src/graphics.rs @@ -1,5 +1,8 @@ use bitflags::bitflags; -use std::cmp::{max, min}; +use std::{ + cmp::{max, min}, + str::FromStr, +}; #[derive(Debug, Clone, Copy, PartialEq)] /// UNSTABLE @@ -237,6 +240,25 @@ bitflags! { } } +impl FromStr for Modifier { + type Err = &'static str; + + fn from_str(modifier: &str) -> Result { + match modifier { + "bold" => Ok(Self::BOLD), + "dim" => Ok(Self::DIM), + "italic" => Ok(Self::ITALIC), + "underlined" => Ok(Self::UNDERLINED), + "slow_blink" => Ok(Self::SLOW_BLINK), + "rapid_blink" => Ok(Self::RAPID_BLINK), + "reversed" => Ok(Self::REVERSED), + "hidden" => Ok(Self::HIDDEN), + "crossed_out" => Ok(Self::CROSSED_OUT), + _ => Err("Invalid modifier"), + } + } +} + /// Style let you control the main characteristics of the displayed elements. /// /// ```rust diff --git a/helix-view/src/theme.rs b/helix-view/src/theme.rs index 756e34f6ee57..74b817d043f8 100644 --- a/helix-view/src/theme.rs +++ b/helix-view/src/theme.rs @@ -1,5 +1,6 @@ use std::{ collections::HashMap, + convert::TryFrom, path::{Path, PathBuf}, }; @@ -11,8 +12,6 @@ use toml::Value; pub use crate::graphics::{Color, Modifier, Style}; -/// Color theme for syntax highlighting. - pub static DEFAULT_THEME: Lazy = Lazy::new(|| { toml::from_slice(include_bytes!("../../theme.toml")).expect("Failed to parse default theme") }); @@ -54,22 +53,10 @@ impl Loader { .map(|entries| { entries .filter_map(|entry| { - if let Ok(entry) = entry { - let path = entry.path(); - if let Some(ext) = path.extension() { - if ext != "toml" { - return None; - } - return Some( - entry - .file_name() - .to_string_lossy() - .trim_end_matches(".toml") - .to_owned(), - ); - } - } - None + let entry = entry.ok()?; + let path = entry.path(); + (path.extension()? == "toml") + .then(|| path.file_stem().unwrap().to_string_lossy().into_owned()) }) .collect() }) @@ -103,13 +90,23 @@ impl<'de> Deserialize<'de> for Theme { let mut styles = HashMap::new(); if let Ok(mut colors) = HashMap::::deserialize(deserializer) { - let palette = parse_palette(colors.remove("palette")); - // scopes.reserve(colors.len()); + // TODO: alert user of parsing failures in editor + let palette = colors + .remove("palette") + .map(|value| { + ThemePalette::try_from(value).unwrap_or_else(|err| { + warn!("{}", err); + ThemePalette::default() + }) + }) + .unwrap_or_default(); + styles.reserve(colors.len()); for (name, style_value) in colors { let mut style = Style::default(); - parse_style(&mut style, style_value, &palette); - // scopes.push(name); + if let Err(err) = palette.parse_style(&mut style, style_value) { + warn!("{}", err); + } styles.insert(name, style); } } @@ -119,121 +116,120 @@ impl<'de> Deserialize<'de> for Theme { } } -fn parse_palette(value: Option) -> HashMap { - match value { - Some(Value::Table(entries)) => entries, - _ => return HashMap::default(), +impl Theme { + pub fn get(&self, scope: &str) -> Style { + self.try_get(scope) + .unwrap_or_else(|| Style::default().fg(Color::Rgb(0, 0, 255))) } - .into_iter() - .filter_map(|(name, value)| { - let color = parse_color(value, &HashMap::default())?; - Some((name, color)) - }) - .collect() -} -fn parse_style(style: &mut Style, value: Value, palette: &HashMap) { - //TODO: alert user of parsing failures - if let Value::Table(entries) = value { - for (name, value) in entries { - match name.as_str() { - "fg" => { - if let Some(color) = parse_color(value, palette) { - *style = style.fg(color); - } - } - "bg" => { - if let Some(color) = parse_color(value, palette) { - *style = style.bg(color); - } - } - "modifiers" => { - if let Value::Array(arr) = value { - for modifier in arr.iter().filter_map(parse_modifier) { - *style = style.add_modifier(modifier); - } - } - } - _ => (), - } - } - } else if let Some(color) = parse_color(value, palette) { - *style = style.fg(color); + pub fn try_get(&self, scope: &str) -> Option