diff --git a/Cargo.lock b/Cargo.lock index 6f38f0034..2b8a25c85 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -62,9 +62,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.79" +version = "1.0.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" +checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" [[package]] name = "arc-swap" @@ -168,9 +168,9 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.33" +version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f13690e35a5e4ace198e7beea2895d29f3a9cc55015fcebe6336bd2010af9eb" +checksum = "5bc015644b92d5890fab7489e49d21f879d5c990186827d42ec511919404f38b" dependencies = [ "android-tzdata", "iana-time-zone", @@ -2231,9 +2231,9 @@ dependencies = [ [[package]] name = "textwrap" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" +checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" dependencies = [ "smawk", "unicode-linebreak", diff --git a/book/src/generated/lang-support.md b/book/src/generated/lang-support.md index bfe6d6b1e..7aec37778 100644 --- a/book/src/generated/lang-support.md +++ b/book/src/generated/lang-support.md @@ -30,6 +30,7 @@ | devicetree | ✓ | | | | | dhall | ✓ | ✓ | | `dhall-lsp-server` | | diff | ✓ | | | | +| docker-compose | ✓ | | ✓ | `docker-compose-langserver` | | dockerfile | ✓ | | | `docker-langserver` | | dot | ✓ | | | `dot-language-server` | | dtd | ✓ | | | | @@ -64,10 +65,11 @@ | gotmpl | ✓ | | | `gopls` | | gowork | ✓ | | | `gopls` | | graphql | ✓ | | | `graphql-lsp` | +| groovy | ✓ | | | | | hare | ✓ | | | | | haskell | ✓ | ✓ | | `haskell-language-server-wrapper` | | haskell-persistent | ✓ | | | | -| hcl | ✓ | | ✓ | `terraform-ls` | +| hcl | ✓ | ✓ | ✓ | `terraform-ls` | | heex | ✓ | ✓ | | `elixir-ls` | | hocon | ✓ | | ✓ | | | hoon | ✓ | | | | @@ -111,7 +113,7 @@ | nasm | ✓ | ✓ | | | | nickel | ✓ | | ✓ | `nls` | | nim | ✓ | ✓ | ✓ | `nimlangserver` | -| nix | ✓ | | | `nil` | +| nix | ✓ | ✓ | | `nil` | | nu | ✓ | | | `nu` | | nunjucks | ✓ | | | | | ocaml | ✓ | | ✓ | `ocamllsp` | diff --git a/book/src/keymap.md b/book/src/keymap.md index a3e41666f..ac84147cd 100644 --- a/book/src/keymap.md +++ b/book/src/keymap.md @@ -53,8 +53,8 @@ Normal mode is the default mode when you launch helix. You can return to it from | `End` | Move to the end of the line | `goto_line_end` | | `Ctrl-b`, `PageUp` | Move page up | `page_up` | | `Ctrl-f`, `PageDown` | Move page down | `page_down` | -| `Ctrl-u` | Move half page up | `half_page_up` | -| `Ctrl-d` | Move half page down | `half_page_down` | +| `Ctrl-u` | Move cursor and page half page up | `page_cursor_half_up` | +| `Ctrl-d` | Move cursor and page half page down | `page_cursor_half_down` | | `Ctrl-i` | Jump forward on the jumplist | `jump_forward` | | `Ctrl-o` | Jump backward on the jumplist | `jump_backward` | | `Ctrl-s` | Save the current selection to the jumplist | `save_selection` | @@ -182,18 +182,18 @@ normal mode) is persistent and can be exited using the escape key. This is useful when you're simply looking over text and not actively editing it. -| Key | Description | Command | -| ----- | ----------- | ------- | -| `z`, `c` | Vertically center the line | `align_view_center` | -| `t` | Align the line to the top of the screen | `align_view_top` | -| `b` | Align the line to the bottom of the screen | `align_view_bottom` | -| `m` | Align the line to the middle of the screen (horizontally) | `align_view_middle` | -| `j`, `down` | Scroll the view downwards | `scroll_down` | -| `k`, `up` | Scroll the view upwards | `scroll_up` | -| `Ctrl-f`, `PageDown` | Move page down | `page_down` | -| `Ctrl-b`, `PageUp` | Move page up | `page_up` | -| `Ctrl-d` | Move half page down | `half_page_down` | -| `Ctrl-u` | Move half page up | `half_page_up` | +| Key | Description | Command | +| ----- | ----------- | ------- | +| `z`, `c` | Vertically center the line | `align_view_center` | +| `t` | Align the line to the top of the screen | `align_view_top` | +| `b` | Align the line to the bottom of the screen | `align_view_bottom` | +| `m` | Align the line to the middle of the screen (horizontally) | `align_view_middle` | +| `j`, `down` | Scroll the view downwards | `scroll_down` | +| `k`, `up` | Scroll the view upwards | `scroll_up` | +| `Ctrl-f`, `PageDown` | Move page down | `page_down` | +| `Ctrl-b`, `PageUp` | Move page up | `page_up` | +| `Ctrl-u` | Move cursor and page half page up | `page_cursor_half_up` | +| `Ctrl-d` | Move cursor and page half page down | `page_cursor_half_down` | #### Goto mode diff --git a/contrib/completion/hx.bash b/contrib/completion/hx.bash index 01b42deb6..62ca029bf 100644 --- a/contrib/completion/hx.bash +++ b/contrib/completion/hx.bash @@ -5,19 +5,20 @@ _hx() { # $1 command name # $2 word being completed # $3 word preceding - COMPREPLY=() case "$3" in -g | --grammar) - COMPREPLY=($(compgen -W "fetch build" -- $2)) + COMPREPLY="$(compgen -W 'fetch build' -- $2)" ;; --health) local languages=$(hx --health |tail -n '+7' |awk '{print $1}' |sed 's/\x1b\[[0-9;]*m//g') - COMPREPLY=($(compgen -W "$languages" -- $2)) + COMPREPLY="$(compgen -W """$languages""" -- $2)" ;; *) - COMPREPLY=($(compgen -fd -W "-h --help --tutor -V --version -v -vv -vvv --health -g --grammar --vsplit --hsplit -c --config --log" -- $2)) + COMPREPLY="$(compgen -fd -W "-h --help --tutor -V --version -v -vv -vvv --health -g --grammar --vsplit --hsplit -c --config --log" -- """$2""")" ;; esac -} && complete -o filenames -F _hx hx + local IFS=$'\n' + COMPREPLY=($COMPREPLY) +} && complete -o filenames -F _hx hx diff --git a/helix-core/Cargo.toml b/helix-core/Cargo.toml index ca2f505c6..0b0dd7452 100644 --- a/helix-core/Cargo.toml +++ b/helix-core/Cargo.toml @@ -48,7 +48,7 @@ encoding_rs = "0.8" chrono = { version = "0.4", default-features = false, features = ["alloc", "std"] } etcetera = "0.8" -textwrap = "0.16.0" +textwrap = "0.16.1" nucleo.workspace = true parking_lot = "0.12" diff --git a/helix-loader/src/lib.rs b/helix-loader/src/lib.rs index f8fac6703..93488e452 100644 --- a/helix-loader/src/lib.rs +++ b/helix-loader/src/lib.rs @@ -53,7 +53,7 @@ fn prioritize_runtime_dirs() -> Vec { rt_dirs.push(conf_rt_dir); if let Ok(dir) = std::env::var("HELIX_RUNTIME") { - let dir = path::expand_tilde(dir); + let dir = path::expand_tilde(Path::new(&dir)); rt_dirs.push(path::normalize(dir)); } diff --git a/helix-lsp/src/client.rs b/helix-lsp/src/client.rs index 0d3a2a56e..8d03d7992 100644 --- a/helix-lsp/src/client.rs +++ b/helix-lsp/src/client.rs @@ -1017,7 +1017,7 @@ impl Client { pub fn resolve_completion_item( &self, completion_item: lsp::CompletionItem, - ) -> Option>> { + ) -> Option>> { let capabilities = self.capabilities.get().unwrap(); // Return early if the server does not support resolving completion items. @@ -1029,7 +1029,8 @@ impl Client { _ => return None, } - Some(self.call::(completion_item)) + let res = self.call::(completion_item); + Some(async move { Ok(serde_json::from_value(res.await?)?) }) } pub fn resolve_code_action( diff --git a/helix-stdx/src/path.rs b/helix-stdx/src/path.rs index 5746657c3..1dc4d0b24 100644 --- a/helix-stdx/src/path.rs +++ b/helix-stdx/src/path.rs @@ -1,6 +1,9 @@ pub use etcetera::home_dir; -use std::path::{Component, Path, PathBuf}; +use std::{ + borrow::Cow, + path::{Component, Path, PathBuf}, +}; use crate::env::current_working_dir; @@ -19,19 +22,22 @@ pub fn fold_home_dir(path: &Path) -> PathBuf { /// Expands tilde `~` into users home directory if available, otherwise returns the path /// unchanged. The tilde will only be expanded when present as the first component of the path /// and only slash follows it. -pub fn expand_tilde(path: impl AsRef) -> PathBuf { - let path = path.as_ref(); - let mut components = path.components().peekable(); - if let Some(Component::Normal(c)) = components.peek() { - if c == &"~" { - if let Ok(home) = home_dir() { - // it's ok to unwrap, the path starts with `~` - return home.join(path.strip_prefix("~").unwrap()); +pub fn expand_tilde<'a, P>(path: P) -> Cow<'a, Path> +where + P: Into>, +{ + let path = path.into(); + let mut components = path.components(); + if let Some(Component::Normal(c)) = components.next() { + if c == "~" { + if let Ok(mut buf) = home_dir() { + buf.push(components); + return Cow::Owned(buf); } } } - path.to_path_buf() + path } /// Normalize a path without resolving symlinks. @@ -109,9 +115,9 @@ pub fn normalize(path: impl AsRef) -> PathBuf { /// This function is used instead of [`std::fs::canonicalize`] because we don't want to verify /// here if the path exists, just normalize it's components. pub fn canonicalize(path: impl AsRef) -> PathBuf { - let path = expand_tilde(path); + let path = expand_tilde(path.as_ref()); let path = if path.is_relative() { - current_working_dir().join(path) + Cow::Owned(current_working_dir().join(path)) } else { path }; @@ -183,3 +189,32 @@ pub fn get_truncated_path(path: impl AsRef) -> PathBuf { ret.push(file); ret } + +#[cfg(test)] +mod tests { + use std::{ + ffi::OsStr, + path::{Component, Path}, + }; + + use crate::path; + + #[test] + fn expand_tilde() { + for path in ["~", "~/foo"] { + let expanded = path::expand_tilde(Path::new(path)); + + let tilde = Component::Normal(OsStr::new("~")); + + let mut component_count = 0; + for component in expanded.components() { + // No tilde left. + assert_ne!(component, tilde); + component_count += 1; + } + + // The path was at least expanded to something. + assert_ne!(component_count, 0); + } + } +} diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 6671265c8..9808f88e2 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -277,6 +277,10 @@ impl MappableCommand { page_down, "Move page down", half_page_up, "Move half page up", half_page_down, "Move half page down", + page_cursor_up, "Move page and cursor up", + page_cursor_down, "Move page and cursor down", + page_cursor_half_up, "Move page and cursor half up", + page_cursor_half_down, "Move page and cursor half down", select_all, "Select whole document", select_regex, "Select all regex matches inside selections", split_selection, "Split selections on regex matches", @@ -1609,7 +1613,7 @@ fn switch_to_lowercase(cx: &mut Context) { }); } -pub fn scroll(cx: &mut Context, offset: usize, direction: Direction) { +pub fn scroll(cx: &mut Context, offset: usize, direction: Direction, sync_cursor: bool) { use Direction::*; let config = cx.editor.config(); let (view, doc) = current!(cx.editor); @@ -1629,7 +1633,7 @@ pub fn scroll(cx: &mut Context, offset: usize, direction: Direction) { let doc_text = doc.text().slice(..); let viewport = view.inner_area(doc); let text_fmt = doc.text_format(viewport.width, None); - let annotations = view.text_annotations(doc, None); + let mut annotations = view.text_annotations(doc, None); (view.offset.anchor, view.offset.vertical_offset) = char_idx_at_visual_offset( doc_text, view.offset.anchor, @@ -1639,6 +1643,30 @@ pub fn scroll(cx: &mut Context, offset: usize, direction: Direction) { &annotations, ); + if sync_cursor { + let movement = match cx.editor.mode { + Mode::Select => Movement::Extend, + _ => Movement::Move, + }; + // TODO: When inline diagnostics gets merged- 1. move_vertically_visual removes + // line annotations/diagnostics so the cursor may jump further than the view. + // 2. If the cursor lands on a complete line of virtual text, the cursor will + // jump a different distance than the view. + let selection = doc.selection(view.id).clone().transform(|range| { + move_vertically_visual( + doc_text, + range, + direction, + offset.unsigned_abs(), + movement, + &text_fmt, + &mut annotations, + ) + }); + doc.set_selection(view.id, selection); + return; + } + let mut head; match direction { Forward => { @@ -1689,25 +1717,49 @@ pub fn scroll(cx: &mut Context, offset: usize, direction: Direction) { fn page_up(cx: &mut Context) { let view = view!(cx.editor); let offset = view.inner_height(); - scroll(cx, offset, Direction::Backward); + scroll(cx, offset, Direction::Backward, false); } fn page_down(cx: &mut Context) { let view = view!(cx.editor); let offset = view.inner_height(); - scroll(cx, offset, Direction::Forward); + scroll(cx, offset, Direction::Forward, false); } fn half_page_up(cx: &mut Context) { let view = view!(cx.editor); let offset = view.inner_height() / 2; - scroll(cx, offset, Direction::Backward); + scroll(cx, offset, Direction::Backward, false); } fn half_page_down(cx: &mut Context) { let view = view!(cx.editor); let offset = view.inner_height() / 2; - scroll(cx, offset, Direction::Forward); + scroll(cx, offset, Direction::Forward, false); +} + +fn page_cursor_up(cx: &mut Context) { + let view = view!(cx.editor); + let offset = view.inner_height(); + scroll(cx, offset, Direction::Backward, true); +} + +fn page_cursor_down(cx: &mut Context) { + let view = view!(cx.editor); + let offset = view.inner_height(); + scroll(cx, offset, Direction::Forward, true); +} + +fn page_cursor_half_up(cx: &mut Context) { + let view = view!(cx.editor); + let offset = view.inner_height() / 2; + scroll(cx, offset, Direction::Backward, true); +} + +fn page_cursor_half_down(cx: &mut Context) { + let view = view!(cx.editor); + let offset = view.inner_height() / 2; + scroll(cx, offset, Direction::Forward, true); } #[allow(deprecated)] @@ -4875,11 +4927,11 @@ fn align_view_middle(cx: &mut Context) { } fn scroll_up(cx: &mut Context) { - scroll(cx, cx.count(), Direction::Backward); + scroll(cx, cx.count(), Direction::Backward, false); } fn scroll_down(cx: &mut Context) { - scroll(cx, cx.count(), Direction::Forward); + scroll(cx, cx.count(), Direction::Forward, false); } fn goto_ts_object_impl(cx: &mut Context, object: &'static str, direction: Direction) { diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index b7ceeba59..3d7ea3fc8 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -110,14 +110,14 @@ fn open(cx: &mut compositor::Context, args: &[Cow], event: PromptEvent) -> ensure!(!args.is_empty(), "wrong argument count"); for arg in args { let (path, pos) = args::parse_file(arg); - let path = helix_stdx::path::expand_tilde(&path); + let path = helix_stdx::path::expand_tilde(path); // If the path is a directory, open a file picker on that directory and update the status // message if let Ok(true) = std::fs::canonicalize(&path).map(|p| p.is_dir()) { let callback = async move { let call: job::Callback = job::Callback::EditorCompositor(Box::new( move |editor: &mut Editor, compositor: &mut Compositor| { - let picker = ui::file_picker(path, &editor.config()); + let picker = ui::file_picker(path.into_owned(), &editor.config()); compositor.push(Box::new(overlaid(picker))); }, )); @@ -1078,11 +1078,11 @@ fn change_current_directory( return Ok(()); } - let dir = helix_stdx::path::expand_tilde( - args.first() - .context("target directory not provided")? - .as_ref(), - ); + let dir = args + .first() + .context("target directory not provided")? + .as_ref(); + let dir = helix_stdx::path::expand_tilde(Path::new(dir)); helix_stdx::env::set_current_working_dir(dir)?; diff --git a/helix-term/src/handlers/completion.rs b/helix-term/src/handlers/completion.rs index d71fd24fc..491ca5638 100644 --- a/helix-term/src/handlers/completion.rs +++ b/helix-term/src/handlers/completion.rs @@ -221,9 +221,17 @@ fn request_completion( .iter() .find(|&trigger| trigger_text.ends_with(trigger)) }); - lsp::CompletionContext { - trigger_kind: lsp::CompletionTriggerKind::TRIGGER_CHARACTER, - trigger_character: trigger_char.cloned(), + + if trigger_char.is_some() { + lsp::CompletionContext { + trigger_kind: lsp::CompletionTriggerKind::TRIGGER_CHARACTER, + trigger_character: trigger_char.cloned(), + } + } else { + lsp::CompletionContext { + trigger_kind: lsp::CompletionTriggerKind::INVOKED, + trigger_character: None, + } } }; diff --git a/helix-term/src/keymap/default.rs b/helix-term/src/keymap/default.rs index 763ed4ae7..92d6b5906 100644 --- a/helix-term/src/keymap/default.rs +++ b/helix-term/src/keymap/default.rs @@ -178,8 +178,8 @@ pub fn default() -> HashMap { "esc" => normal_mode, "C-b" | "pageup" => page_up, "C-f" | "pagedown" => page_down, - "C-u" => half_page_up, - "C-d" => half_page_down, + "C-u" => page_cursor_half_up, + "C-d" => page_cursor_half_down, "C-w" => { "Window" "C-w" | "w" => rotate_view, @@ -287,8 +287,8 @@ pub fn default() -> HashMap { "j" | "down" => scroll_down, "C-b" | "pageup" => page_up, "C-f" | "pagedown" => page_down, - "C-u" | "backspace" => half_page_up, - "C-d" | "space" => half_page_down, + "C-u" | "backspace" => page_cursor_half_up, + "C-d" | "space" => page_cursor_half_down, "/" => search, "?" => rsearch, @@ -304,8 +304,8 @@ pub fn default() -> HashMap { "j" | "down" => scroll_down, "C-b" | "pageup" => page_up, "C-f" | "pagedown" => page_down, - "C-u" | "backspace" => half_page_up, - "C-d" | "space" => half_page_down, + "C-u" | "backspace" => page_cursor_half_up, + "C-d" | "space" => page_cursor_half_down, "/" => search, "?" => rsearch, diff --git a/helix-term/src/ui/completion.rs b/helix-term/src/ui/completion.rs index 48d97fbd8..6cbb5b109 100644 --- a/helix-term/src/ui/completion.rs +++ b/helix-term/src/ui/completion.rs @@ -1,7 +1,9 @@ use crate::{ compositor::{Component, Context, Event, EventResult}, handlers::trigger_auto_completion, + job, }; +use helix_event::AsyncHook; use helix_view::{ document::SavePoint, editor::CompleteAction, @@ -10,14 +12,14 @@ use helix_view::{ theme::{Modifier, Style}, ViewId, }; +use tokio::time::Instant; use tui::{buffer::Buffer as Surface, text::Span}; -use std::{borrow::Cow, sync::Arc}; +use std::{borrow::Cow, sync::Arc, time::Duration}; use helix_core::{chars, Change, Transaction}; use helix_view::{graphics::Rect, Document, Editor}; -use crate::commands; use crate::ui::{menu, Markdown, Menu, Popup, PromptEvent}; use helix_lsp::{lsp, util, OffsetEncoding}; @@ -102,6 +104,7 @@ pub struct Completion { #[allow(dead_code)] trigger_offset: usize, filter: String, + resolve_handler: tokio::sync::mpsc::Sender, } impl Completion { @@ -368,6 +371,7 @@ impl Completion { // TODO: expand nucleo api to allow moving straight to a Utf32String here // and avoid allocation during matching filter: String::from(fragment), + resolve_handler: ResolveHandler::default().spawn(), }; // need to recompute immediately in case start_offset != trigger_offset @@ -379,6 +383,8 @@ impl Completion { completion } + /// Synchronously resolve the given completion item. This is used when + /// accepting a completion. fn resolve_completion_item( language_server: &helix_lsp::Client, completion_item: lsp::CompletionItem, @@ -386,7 +392,7 @@ impl Completion { let future = language_server.resolve_completion_item(completion_item)?; let response = helix_lsp::block_on(future); match response { - Ok(value) => serde_json::from_value(value).ok(), + Ok(item) => Some(item), Err(err) => { log::error!("Failed to resolve completion item: {}", err); None @@ -420,62 +426,6 @@ impl Completion { self.popup.contents_mut().replace_option(old_item, new_item); } - /// Asynchronously requests that the currently selection completion item is - /// resolved through LSP `completionItem/resolve`. - pub fn ensure_item_resolved(&mut self, cx: &mut commands::Context) -> bool { - // > If computing full completion items is expensive, servers can additionally provide a - // > handler for the completion item resolve request. ... - // > A typical use case is for example: the `textDocument/completion` request doesn't fill - // > in the `documentation` property for returned completion items since it is expensive - // > to compute. When the item is selected in the user interface then a - // > 'completionItem/resolve' request is sent with the selected completion item as a parameter. - // > The returned completion item should have the documentation property filled in. - // https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_completion - let current_item = match self.popup.contents().selection() { - Some(item) if !item.resolved => item.clone(), - _ => return false, - }; - - let Some(language_server) = cx - .editor - .language_server_by_id(current_item.language_server_id) - else { - return false; - }; - - // This method should not block the compositor so we handle the response asynchronously. - let Some(future) = language_server.resolve_completion_item(current_item.item.clone()) - else { - return false; - }; - - cx.callback( - future, - move |_editor, compositor, response: Option| { - let resolved_item = match response { - Some(item) => item, - None => return, - }; - - if let Some(completion) = &mut compositor - .find::() - .unwrap() - .completion - { - let resolved_item = CompletionItem { - item: resolved_item, - language_server_id: current_item.language_server_id, - resolved: true, - }; - - completion.replace_item(current_item, resolved_item); - } - }, - ); - - true - } - pub fn area(&mut self, viewport: Rect, editor: &Editor) -> Rect { self.popup.area(viewport, editor) } @@ -498,6 +448,9 @@ impl Component for Completion { Some(option) => option, None => return, }; + if !option.resolved { + helix_event::send_blocking(&self.resolve_handler, option.clone()); + } // need to render: // option.detail // --- @@ -599,3 +552,88 @@ impl Component for Completion { markdown_doc.render(doc_area, surface, cx); } } + +/// A hook for resolving incomplete completion items. +/// +/// From the [LSP spec](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_completion): +/// +/// > If computing full completion items is expensive, servers can additionally provide a +/// > handler for the completion item resolve request. ... +/// > A typical use case is for example: the `textDocument/completion` request doesn't fill +/// > in the `documentation` property for returned completion items since it is expensive +/// > to compute. When the item is selected in the user interface then a +/// > 'completionItem/resolve' request is sent with the selected completion item as a parameter. +/// > The returned completion item should have the documentation property filled in. +#[derive(Debug, Default)] +struct ResolveHandler { + trigger: Option, + request: Option, +} + +impl AsyncHook for ResolveHandler { + type Event = CompletionItem; + + fn handle_event( + &mut self, + item: Self::Event, + timeout: Option, + ) -> Option { + if self + .trigger + .as_ref() + .is_some_and(|trigger| trigger == &item) + { + timeout + } else { + self.trigger = Some(item); + self.request = None; + Some(Instant::now() + Duration::from_millis(150)) + } + } + + fn finish_debounce(&mut self) { + let Some(item) = self.trigger.take() else { return }; + let (tx, rx) = helix_event::cancelation(); + self.request = Some(tx); + job::dispatch_blocking(move |editor, _| resolve_completion_item(editor, item, rx)) + } +} + +fn resolve_completion_item( + editor: &mut Editor, + item: CompletionItem, + cancel: helix_event::CancelRx, +) { + let Some(language_server) = editor.language_server_by_id(item.language_server_id) else { + return; + }; + + let Some(future) = language_server.resolve_completion_item(item.item.clone()) else { + return; + }; + + tokio::spawn(async move { + match helix_event::cancelable_future(future, cancel).await { + Some(Ok(resolved_item)) => { + job::dispatch(move |_, compositor| { + if let Some(completion) = &mut compositor + .find::() + .unwrap() + .completion + { + let resolved_item = CompletionItem { + item: resolved_item, + language_server_id: item.language_server_id, + resolved: true, + }; + + completion.replace_item(item, resolved_item); + }; + }) + .await + } + Some(Err(err)) => log::error!("completion resolve request failed: {err}"), + None => (), + } + }); +} diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index bb749d2e7..15a7262a8 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -1027,14 +1027,6 @@ impl EditorView { pub fn handle_idle_timeout(&mut self, cx: &mut commands::Context) -> EventResult { commands::compute_inlay_hints_for_all_views(cx.editor, cx.jobs); - if let Some(completion) = &mut self.completion { - return if completion.ensure_item_resolved(cx) { - EventResult::Consumed(None) - } else { - EventResult::Ignored(None) - }; - } - EventResult::Ignored(None) } } @@ -1088,6 +1080,15 @@ impl EditorView { if modifiers == KeyModifiers::ALT { let selection = doc.selection(view_id).clone(); doc.set_selection(view_id, selection.push(Range::point(pos))); + } else if editor.mode == Mode::Select { + // Discards non-primary selections for consistent UX with normal mode + let primary = doc.selection(view_id).primary().put_cursor( + doc.text().slice(..), + pos, + true, + ); + editor.mouse_down_range = Some(primary); + doc.set_selection(view_id, Selection::single(primary.anchor, primary.head)); } else { doc.set_selection(view_id, Selection::point(pos)); } @@ -1156,7 +1157,7 @@ impl EditorView { } let offset = config.scroll_lines.unsigned_abs(); - commands::scroll(cxt, offset, direction); + commands::scroll(cxt, offset, direction, false); cxt.editor.tree.focus = current_view; cxt.editor.ensure_cursor_in_view(current_view); @@ -1171,19 +1172,26 @@ impl EditorView { let (view, doc) = current!(cxt.editor); - if doc - .selection(view.id) - .primary() - .slice(doc.text().slice(..)) - .len_chars() - <= 1 - { - return EventResult::Ignored(None); - } - - commands::MappableCommand::yank_main_selection_to_primary_clipboard.execute(cxt); + let should_yank = match cxt.editor.mouse_down_range.take() { + Some(down_range) => doc.selection(view.id).primary() != down_range, + None => { + // This should not happen under normal cases. We fall back to the original + // behavior of yanking on non-single-char selections. + doc.selection(view.id) + .primary() + .slice(doc.text().slice(..)) + .len_chars() + > 1 + } + }; - EventResult::Consumed(None) + if should_yank { + commands::MappableCommand::yank_main_selection_to_primary_clipboard + .execute(cxt); + EventResult::Consumed(None) + } else { + EventResult::Ignored(None) + } } MouseEventKind::Up(MouseButton::Right) => { diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs index d27e83553..0873116cb 100644 --- a/helix-term/src/ui/mod.rs +++ b/helix-term/src/ui/mod.rs @@ -428,9 +428,9 @@ pub mod completers { path } else { match path.parent() { - Some(path) if !path.as_os_str().is_empty() => path.to_path_buf(), + Some(path) if !path.as_os_str().is_empty() => Cow::Borrowed(path), // Path::new("h")'s parent is Some("")... - _ => helix_stdx::env::current_working_dir(), + _ => Cow::Owned(helix_stdx::env::current_working_dir()), } }; diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 68b74cf00..fffbe6207 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -42,7 +42,7 @@ pub use helix_core::diagnostic::Severity; use helix_core::{ auto_pairs::AutoPairs, syntax::{self, AutoPairConfig, IndentationHeuristic, LanguageServerFeature, SoftWrap}, - Change, LineEnding, Position, Selection, NATIVE_LINE_ENDING, + Change, LineEnding, Position, Range, Selection, NATIVE_LINE_ENDING, }; use helix_dap as dap; use helix_lsp::lsp; @@ -964,6 +964,8 @@ pub struct Editor { /// times during rendering and should not be set by other functions. pub cursor_cache: Cell>>, pub handlers: Handlers, + + pub mouse_down_range: Option, } pub type Motion = Box; @@ -1080,6 +1082,7 @@ impl Editor { needs_redraw: false, cursor_cache: Cell::new(None), handlers, + mouse_down_range: None, } } @@ -1978,7 +1981,7 @@ impl Editor { /// Switches the editor into normal mode. pub fn enter_normal_mode(&mut self) { - use helix_core::{graphemes, Range}; + use helix_core::graphemes; if self.mode == Mode::Normal { return; diff --git a/helix-view/src/handlers/dap.rs b/helix-view/src/handlers/dap.rs index e1437bef7..a5fa0c29c 100644 --- a/helix-view/src/handlers/dap.rs +++ b/helix-view/src/handlers/dap.rs @@ -226,10 +226,15 @@ impl Editor { breakpoints.iter().position(|b| b.id == breakpoint.id) { breakpoints[i].verified = breakpoint.verified; - breakpoints[i].message = breakpoint.message.clone(); - breakpoints[i].line = - breakpoint.line.unwrap().saturating_sub(1); // TODO: no unwrap - breakpoints[i].column = breakpoint.column; + breakpoints[i].message = breakpoint + .message + .clone() + .or_else(|| breakpoints[i].message.take()); + breakpoints[i].line = breakpoint + .line + .map_or(breakpoints[i].line, |line| line.saturating_sub(1)); + breakpoints[i].column = + breakpoint.column.or(breakpoints[i].column); } } } diff --git a/languages.toml b/languages.toml index bfa33bf58..112333ea9 100644 --- a/languages.toml +++ b/languages.toml @@ -23,6 +23,7 @@ cuelsp = { command = "cuelsp" } dart = { command = "dart", args = ["language-server", "--client-id=helix"] } dhall-lsp-server = { command = "dhall-lsp-server" } docker-langserver = { command = "docker-langserver", args = ["--stdio"] } +docker-compose-langserver = { command = "docker-compose-langserver", args = ["--stdio"]} dot-language-server = { command = "dot-language-server", args = ["--stdio"] } elixir-ls = { command = "elixir-ls", config = { elixirLS.dialyzerEnabled = false } } elm-language-server = { command = "elm-language-server" } @@ -860,6 +861,7 @@ file-types = [ "tcshrc", "bashrc_Apple_Terminal", "zshrc_Apple_Terminal", + { glob = "tmux.conf" }, { glob = ".bash_history" }, { glob = ".bash_login" }, { glob = ".bash_logout" }, @@ -1091,7 +1093,7 @@ name = "lua" injection-regex = "lua" scope = "source.lua" file-types = ["lua"] -shebangs = ["lua"] +shebangs = ["lua", "luajit"] roots = [".luarc.json", ".luacheckrc", ".stylua.toml", "selene.toml", ".git"] comment-token = "--" indent = { tab-width = 2, unit = " " } @@ -1459,6 +1461,16 @@ language-servers = [ "docker-langserver" ] name = "dockerfile" source = { git = "https://github.com/camdencheek/tree-sitter-dockerfile", rev = "8ee3a0f7587b2bd8c45c8cb7d28bd414604aec62" } +[[language]] +name = "docker-compose" +scope = "source.yaml.docker-compose" +roots = ["docker-compose.yaml", "docker-compose.yml"] +language-servers = [ "docker-compose-langserver" ] +file-types = [{ glob = "docker-compose.yaml" }, { glob = "docker-compose.yml" }] +comment-token = "#" +indent = { tab-width = 2, unit = " " } +grammar = "yaml" + [[language]] name = "git-commit" scope = "git.commitmsg" @@ -1813,7 +1825,7 @@ injection-regex = "sql" [[grammar]] name = "sql" -source = { git = "https://github.com/DerekStride/tree-sitter-sql", rev = "25be0b8f17e9189ad9e1b875869d025c5aec1286" } +source = { git = "https://github.com/DerekStride/tree-sitter-sql", rev = "da2d1eff425b146d3c8cab7be8dfa98b11d896dc" } [[language]] name = "gdscript" @@ -2359,6 +2371,8 @@ file-types = [ "menu", "mxml", "nuspec", + "osc", + "osm", "pt", "publishsettings", "pubxml", @@ -3113,3 +3127,16 @@ indent = { tab-width = 2, unit = " " } [[grammar]] name = "pkl" source = { git = "https://github.com/apple/tree-sitter-pkl", rev = "c03f04a313b712f8ab00a2d862c10b37318699ae" } + +[[language]] +name = "groovy" +language-id = "groovy" +scope = "source.groovy" +file-types = ["gradle", "groovy", "jenkinsfile", { glob = "Jenkinsfile" }, { glob = "Jenkinsfile.*" }] +shebangs = ["groovy"] +comment-token = "//" +indent = { tab-width = 2, unit = " " } + +[[grammar]] +name = "groovy" +source = { git = "https://github.com/Decodetalkers/tree-sitter-groovy", rev = "7e023227f46fee428b16a0288eeb0f65ee2523ec" } diff --git a/runtime/queries/dart/textobjects.scm b/runtime/queries/dart/textobjects.scm index 028276156..b88b97bc9 100644 --- a/runtime/queries/dart/textobjects.scm +++ b/runtime/queries/dart/textobjects.scm @@ -56,9 +56,34 @@ (documentation_comment)+ @comment.around -(formal_parameter) @parameter.inside +(formal_parameter_list + ( + (formal_parameter) @parameter.inside . ","? @parameter.around + ) @parameter.around +) + +(optional_formal_parameters + ( + (formal_parameter) @parameter.inside . ","? @parameter.around + ) @parameter.around +) + +(arguments + ( + [ + (argument) @parameter.inside + (named_argument (label) . (_)* @parameter.inside) + ] + . ","? @parameter.around + ) @parameter.around +) -(formal_parameter_list) @parameter.around +(type_arguments + ( + ((_) . ("." . (_) @parameter.inside @parameter.around)?) @parameter.inside + . ","? @parameter.around + ) @parameter.around +) (expression_statement ((identifier) @_name (#any-of? @_name "test" "testWidgets")) diff --git a/runtime/queries/docker-compose/highlights.scm b/runtime/queries/docker-compose/highlights.scm new file mode 100644 index 000000000..4ba254e82 --- /dev/null +++ b/runtime/queries/docker-compose/highlights.scm @@ -0,0 +1 @@ +; inherits: yaml diff --git a/runtime/queries/docker-compose/indents.scm b/runtime/queries/docker-compose/indents.scm new file mode 100644 index 000000000..4ba254e82 --- /dev/null +++ b/runtime/queries/docker-compose/indents.scm @@ -0,0 +1 @@ +; inherits: yaml diff --git a/runtime/queries/docker-compose/injections.scm b/runtime/queries/docker-compose/injections.scm new file mode 100644 index 000000000..4ba254e82 --- /dev/null +++ b/runtime/queries/docker-compose/injections.scm @@ -0,0 +1 @@ +; inherits: yaml diff --git a/runtime/queries/groovy/highlights.scm b/runtime/queries/groovy/highlights.scm new file mode 100644 index 000000000..4e94ccd3a --- /dev/null +++ b/runtime/queries/groovy/highlights.scm @@ -0,0 +1,96 @@ +(unit + (identifier) @variable) + +(string + (identifier) @variable) + +(escape_sequence) @constant.character.escape + +(block + (unit + (identifier) @namespace)) + +(func + (identifier) @function) + +(number) @constant.numeric + +((identifier) @constant.builtin.boolean + (#any-of? @constant.builtin.boolean "true" "false")) + +((identifier) @constant + (#match? @constant "^[A-Z][A-Z\\d_]*$")) + +((identifier) @constant.builtin + (#eq? @constant.builtin "null")) + +((identifier) @type + (#any-of? @type + "String" + "Map" + "Object" + "Boolean" + "Integer" + "List")) + +((identifier) @function.builtin + (#any-of? @function.builtin + "void" + "id" + "version" + "apply" + "implementation" + "testImplementation" + "androidTestImplementation" + "debugImplementation")) + +((identifier) @keyword.storage.modifier + (#eq? @keyword.storage.modifier "static")) + +((identifier) @keyword.storage.type + (#any-of? @keyword.storage.type "class" "def" "interface")) + +((identifier) @keyword + (#any-of? @keyword + "assert" + "new" + "extends" + "implements" + "instanceof")) + +((identifier) @keyword.control.import + (#any-of? @keyword.control.import "import" "package")) + +((identifier) @keyword.storage.modifier + (#any-of? @keyword.storage.modifier + "abstract" + "protected" + "private" + "public")) + +((identifier) @keyword.control.exception + (#any-of? @keyword.control.exception + "throw" + "finally" + "try" + "catch")) + +(string) @string + +[ + (line_comment) + (block_comment) +] @comment + +((block_comment) @comment.block.documentation + (#match? @comment.block.documentation "^/[*][*][^*](?s:.)*[*]/$")) + +((line_comment) @comment.block.documentation + (#match? @comment.block.documentation "^///[^/]*.*$")) + +[ + (operators) + (leading_key) +] @operator + +["(" ")" "[" "]" "{" "}"] @punctuation.bracket diff --git a/runtime/queries/groovy/injections.scm b/runtime/queries/groovy/injections.scm new file mode 100644 index 000000000..e4509a5fd --- /dev/null +++ b/runtime/queries/groovy/injections.scm @@ -0,0 +1,2 @@ +([(line_comment) (block_comment)] @injection.content + (#set! injection.language "comment")) diff --git a/runtime/queries/hcl/textobjects.scm b/runtime/queries/hcl/textobjects.scm new file mode 100644 index 000000000..1e6505876 --- /dev/null +++ b/runtime/queries/hcl/textobjects.scm @@ -0,0 +1,6 @@ +(comment) @comment.inside +(comment)+ @comment.around + +(function_arguments + ((_) @parameter.inside . ","? @parameter.around) @parameter.around) + diff --git a/runtime/queries/nix/textobjects.scm b/runtime/queries/nix/textobjects.scm new file mode 100644 index 000000000..1508d4c2b --- /dev/null +++ b/runtime/queries/nix/textobjects.scm @@ -0,0 +1,9 @@ +(comment) @comment.inside +(comment)+ @comment.around + +(formals + ((_) @parameter.inside . ","? @parameter.around) @parameter.around) + +(function_expression + body: (_) @function.inside) @function.around + diff --git a/runtime/queries/rust/textobjects.scm b/runtime/queries/rust/textobjects.scm index 837f981e7..df26331d8 100644 --- a/runtime/queries/rust/textobjects.scm +++ b/runtime/queries/rust/textobjects.scm @@ -34,6 +34,9 @@ (arguments ((_) @parameter.inside . ","? @parameter.around) @parameter.around) +(field_initializer_list + ((_) @parameter.inside . ","? @parameter.around) @parameter.around) + [ (line_comment) (block_comment) diff --git a/runtime/queries/sql/highlights.scm b/runtime/queries/sql/highlights.scm index 09b07489e..e575debc5 100644 --- a/runtime/queries/sql/highlights.scm +++ b/runtime/queries/sql/highlights.scm @@ -24,20 +24,20 @@ (term alias: (identifier) @variable.parameter) -(term +((term value: (cast name: (keyword_cast) @function.builtin - parameter: [(literal)]?)) + parameter: [(literal)]?))) (literal) @string (comment) @comment.line (marginalia) @comment.block ((literal) @constant.numeric.integer - (#match? @constant.numeric.integer "^-?\\d+$")) + (#match? @constant.numeric.integer "^[-+]?\\d+$")) ((literal) @constant.numeric.float - (#match? @constant.numeric.float "^-?\\d*\\.\\d*$")) + (#match? @constant.numeric.float "^[-+]?\\d*\\.\\d*$")) (parameter) @variable.parameter diff --git a/runtime/themes/cyan_light.toml b/runtime/themes/cyan_light.toml index 45cb6539d..a35ad5847 100644 --- a/runtime/themes/cyan_light.toml +++ b/runtime/themes/cyan_light.toml @@ -1,6 +1,10 @@ -# An approximation/port of the Cyan Light Theme from Jetbrains -# -# Original Color Scheme here https://plugins.jetbrains.com/plugin/12102-cyan-light-theme +# Cyan Light +# Adapted from JetBrains' Cyan Light Theme https://plugins.jetbrains.com/plugin/12102-cyan-light-theme +# Author: Abderrahmane Tahri Jouti + +# Original Author : Olga Berdnikova +# LICENSE : MIT +# Source: https://github.com/OlyaB/CyanTheme "attribute" = "blue" "type" = "shade07" diff --git a/runtime/themes/licenses/cyan_light.LICENSE b/runtime/themes/licenses/cyan_light.LICENSE new file mode 100644 index 000000000..3a4a2fb87 --- /dev/null +++ b/runtime/themes/licenses/cyan_light.LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 CloudCannon + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/runtime/themes/monokai_soda.toml b/runtime/themes/monokai_soda.toml new file mode 100644 index 000000000..7e43247e3 --- /dev/null +++ b/runtime/themes/monokai_soda.toml @@ -0,0 +1,120 @@ +# Monokai Soda port for Helix (https://helix-editor.com) +# Author : Jimmy Zelinskie + +# Syntax + +## Constants +"constant" = "white" +"constant.builtin" = "pink" +"constant.character.escape" = "blue" +"constant.numeric" = "purple" + +## Diagnostics +"diagnostic" = { modifiers = ["underlined"] } +"diagnostic.error" = { underline = { style = "curl", color = "pink" } } +"diagnostic.warning" = { underline = { style = "curl", color = "orange" } } +"diagnostic.info" = { underline = { style = "curl", color = "white" } } + +## Diffs +"diff.plus" = "green" +"diff.delta" = "orange" +"diff.minus" = "pink" +"diff.delta.moved" = "orange" + +## Functions +"function" = "green" +"function.macro" = "blue" +"function.builtin" = "pink" +"constructor" = "blue" + +## Keywords +"keyword" = "pink" +"keyword.directive" = "blue" + +## Punctuation +"punctuation" = "gray" + +## Strings +"string" = "yellow" + +## Types +"type" = "blue" +"type.builtin" = "pink" + +## Variables +"variable" = "white" +"variable.builtin" = "pink" +"variable.other.member" = "white" +"variable.parameter" = "softorange" + +## Markup +"markup.heading" = "green" +"markup.bold" = { fg = "orange", modifiers = ["bold"] } +"markup.italic" = { fg = "orange", modifiers = ["italic"] } +"markup.link.url" = { fg = "orange", modifiers = ["underlined"] } +"markup.link.text" = "yellow" +"markup.quote" = "green" + +## Misc +"attribute" = "blue" +"comment" = { fg = "gray", modifiers = ["italic"] } +"error" = "pink" +"hint" = "white" +"info" = "white" +"label" = "yellow" +"module" = "softorange" +"namespace" = "pink" +"operator" = "pink" +"special" = "softorange" +"warning" = "orange" + +# Editor UI + +## Main +"ui.background" = { bg = "background" } +"ui.text" = "white" +"ui.window" = { bg = "darkgray" } + +## Debug (TODO) + +## Menus +"ui.menu" = { fg = "white", bg = "darkgray" } +"ui.menu.selected" = { modifiers = ["reversed"] } +"ui.popup" = { bg = "darkgray" } +"ui.help" = { fg = "white", bg = "darkgray" } + +## Gutter +"ui.linenr" = "darkgray" +"ui.linenr.selected" = "orange" + +## Cursor +"ui.cursor.primary" = { fg = "white", modifiers = ["reversed"] } +"ui.cursor.match" = { fg = "white", modifiers = ["reversed"] } +"ui.selection" = { bg = "darkgray" } + +## Statusline +"ui.statusline" = { bg = "darkgray" } +"ui.statusline.inactive" = { fg = "white", bg = "darkgray" } +"ui.statusline.normal" = { fg = "white", bg = "blue" } +"ui.statusline.insert" = { fg = "white", bg = "green" } +"ui.statusline.select" = { fg = "white", bg = "purple" } + +"ui.text.focus" = { fg = "yellow", modifiers = ["bold"] } +"ui.virtual" = "darkgray" +"ui.virtual.ruler" = { bg = "darkgray" } + +# Palette + +[palette] +"purple" = "#AE81FF" +"yellow" = "#E6DB74" +"pink" = "#f92a72" +"white" = "#cfcfc2" +"gray" = "#75715e" +"darkgray" = "#444444" +"black" = "#222222" +"blue" = "#66d9ef" +"green" = "#a6e22e" +"softorange" = "#f59762" +"orange" = "#fd971f" +"background" = "#191919" diff --git a/runtime/themes/sonokai.toml b/runtime/themes/sonokai.toml index f586be28d..4bbdb4dbe 100644 --- a/runtime/themes/sonokai.toml +++ b/runtime/themes/sonokai.toml @@ -6,24 +6,23 @@ # License: MIT License "type" = "blue" -"constant" = "purple" +"constant" = "fg" "constant.numeric" = "purple" "constant.character.escape" = "orange" "string" = "yellow" "comment" = "grey" "variable" = "fg" -"variable.builtin" = "orange" +"variable.builtin" = "purple" "variable.parameter" = "fg" -"variable.other.member" = "fg" -"label" = "orange" +"variable.other.member" = "orange" +"label" = "red" "punctuation" = "grey" -"punctuation.delimiter" = "grey" -"punctuation.bracket" = "fg" +"punctuation.special" = "yellow" "keyword" = "red" -"operator" = "orange" +"operator" = "red" "function" = "green" -"function.builtin" = "blue" -"function.macro" = "purple" +"function.builtin" = "green" +"function.macro" = "green" "tag" = "yellow" "namespace" = "blue" "attribute" = "purple" @@ -48,12 +47,12 @@ "markup.raw" = "green" "diff.plus" = "green" -"diff.delta" = "orange" +"diff.delta" = "blue" "diff.minus" = "red" "ui.background" = { bg = "bg0" } "ui.cursor" = { modifiers = ['reversed'] } -"ui.cursor.match" = { fg = "orange", bg = "diff_yellow" } +"ui.cursor.match" = { bg = "bg4" } "ui.cursor.insert" = { fg = "black", bg = "grey" } "ui.cursor.select" = { fg = "bg0", bg = "blue" } "ui.selection" = { bg = "bg5" } @@ -73,7 +72,7 @@ "ui.text.focus" = "green" "ui.menu" = { fg = "fg", bg = "bg2" } "ui.menu.selected" = { fg = "bg0", bg = "green" } -"ui.virtual.whitespace" = { fg = "grey_dim" } +"ui.virtual.whitespace" = "bg4" "ui.virtual.ruler" = { bg = "bg3" } "ui.virtual.inlay-hint" = { fg = "grey_dim" } @@ -92,11 +91,12 @@ error = { fg = 'red', bg = 'bg2', modifiers = ['bold'] } [palette] black = "#181819" +bg_dim = "#222327" bg0 = "#2c2e34" bg1 = "#33353f" bg2 = "#363944" bg3 = "#3b3e48" -bg4 = "#5C606A" +bg4 = "#414550" bg5 = "#444852" bg_red = "#ff6077" diff_red = "#55393d"