Merge branch 'helix-editor:master' into copy_between_registers

pull/9717/head
Vivek Kethineni 9 months ago committed by GitHub
commit 7e001b98d0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

12
Cargo.lock generated

@ -62,9 +62,9 @@ dependencies = [
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.79" version = "1.0.80"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1"
[[package]] [[package]]
name = "arc-swap" name = "arc-swap"
@ -168,9 +168,9 @@ dependencies = [
[[package]] [[package]]
name = "chrono" name = "chrono"
version = "0.4.33" version = "0.4.34"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f13690e35a5e4ace198e7beea2895d29f3a9cc55015fcebe6336bd2010af9eb" checksum = "5bc015644b92d5890fab7489e49d21f879d5c990186827d42ec511919404f38b"
dependencies = [ dependencies = [
"android-tzdata", "android-tzdata",
"iana-time-zone", "iana-time-zone",
@ -2231,9 +2231,9 @@ dependencies = [
[[package]] [[package]]
name = "textwrap" name = "textwrap"
version = "0.16.0" version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9"
dependencies = [ dependencies = [
"smawk", "smawk",
"unicode-linebreak", "unicode-linebreak",

@ -30,6 +30,7 @@
| devicetree | ✓ | | | | | devicetree | ✓ | | | |
| dhall | ✓ | ✓ | | `dhall-lsp-server` | | dhall | ✓ | ✓ | | `dhall-lsp-server` |
| diff | ✓ | | | | | diff | ✓ | | | |
| docker-compose | ✓ | | ✓ | `docker-compose-langserver` |
| dockerfile | ✓ | | | `docker-langserver` | | dockerfile | ✓ | | | `docker-langserver` |
| dot | ✓ | | | `dot-language-server` | | dot | ✓ | | | `dot-language-server` |
| dtd | ✓ | | | | | dtd | ✓ | | | |
@ -64,10 +65,11 @@
| gotmpl | ✓ | | | `gopls` | | gotmpl | ✓ | | | `gopls` |
| gowork | ✓ | | | `gopls` | | gowork | ✓ | | | `gopls` |
| graphql | ✓ | | | `graphql-lsp` | | graphql | ✓ | | | `graphql-lsp` |
| groovy | ✓ | | | |
| hare | ✓ | | | | | hare | ✓ | | | |
| haskell | ✓ | ✓ | | `haskell-language-server-wrapper` | | haskell | ✓ | ✓ | | `haskell-language-server-wrapper` |
| haskell-persistent | ✓ | | | | | haskell-persistent | ✓ | | | |
| hcl | ✓ | | ✓ | `terraform-ls` | | hcl | ✓ | | ✓ | `terraform-ls` |
| heex | ✓ | ✓ | | `elixir-ls` | | heex | ✓ | ✓ | | `elixir-ls` |
| hocon | ✓ | | ✓ | | | hocon | ✓ | | ✓ | |
| hoon | ✓ | | | | | hoon | ✓ | | | |
@ -111,7 +113,7 @@
| nasm | ✓ | ✓ | | | | nasm | ✓ | ✓ | | |
| nickel | ✓ | | ✓ | `nls` | | nickel | ✓ | | ✓ | `nls` |
| nim | ✓ | ✓ | ✓ | `nimlangserver` | | nim | ✓ | ✓ | ✓ | `nimlangserver` |
| nix | ✓ | | | `nil` | | nix | ✓ | | | `nil` |
| nu | ✓ | | | `nu` | | nu | ✓ | | | `nu` |
| nunjucks | ✓ | | | | | nunjucks | ✓ | | | |
| ocaml | ✓ | | ✓ | `ocamllsp` | | ocaml | ✓ | | ✓ | `ocamllsp` |

@ -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` | | `End` | Move to the end of the line | `goto_line_end` |
| `Ctrl-b`, `PageUp` | Move page up | `page_up` | | `Ctrl-b`, `PageUp` | Move page up | `page_up` |
| `Ctrl-f`, `PageDown` | Move page down | `page_down` | | `Ctrl-f`, `PageDown` | Move page down | `page_down` |
| `Ctrl-u` | Move half page up | `half_page_up` | | `Ctrl-u` | Move cursor and page half page up | `page_cursor_half_up` |
| `Ctrl-d` | Move half page down | `half_page_down` | | `Ctrl-d` | Move cursor and page half page down | `page_cursor_half_down` |
| `Ctrl-i` | Jump forward on the jumplist | `jump_forward` | | `Ctrl-i` | Jump forward on the jumplist | `jump_forward` |
| `Ctrl-o` | Jump backward on the jumplist | `jump_backward` | | `Ctrl-o` | Jump backward on the jumplist | `jump_backward` |
| `Ctrl-s` | Save the current selection to the jumplist | `save_selection` | | `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. useful when you're simply looking over text and not actively editing it.
| Key | Description | Command | | Key | Description | Command |
| ----- | ----------- | ------- | | ----- | ----------- | ------- |
| `z`, `c` | Vertically center the line | `align_view_center` | | `z`, `c` | Vertically center the line | `align_view_center` |
| `t` | Align the line to the top of the screen | `align_view_top` | | `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` | | `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` | | `m` | Align the line to the middle of the screen (horizontally) | `align_view_middle` |
| `j`, `down` | Scroll the view downwards | `scroll_down` | | `j`, `down` | Scroll the view downwards | `scroll_down` |
| `k`, `up` | Scroll the view upwards | `scroll_up` | | `k`, `up` | Scroll the view upwards | `scroll_up` |
| `Ctrl-f`, `PageDown` | Move page down | `page_down` | | `Ctrl-f`, `PageDown` | Move page down | `page_down` |
| `Ctrl-b`, `PageUp` | Move page up | `page_up` | | `Ctrl-b`, `PageUp` | Move page up | `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-u` | Move half page up | `half_page_up` | | `Ctrl-d` | Move cursor and page half page down | `page_cursor_half_down` |
#### Goto mode #### Goto mode

@ -5,19 +5,20 @@ _hx() {
# $1 command name # $1 command name
# $2 word being completed # $2 word being completed
# $3 word preceding # $3 word preceding
COMPREPLY=()
case "$3" in case "$3" in
-g | --grammar) -g | --grammar)
COMPREPLY=($(compgen -W "fetch build" -- $2)) COMPREPLY="$(compgen -W 'fetch build' -- $2)"
;; ;;
--health) --health)
local languages=$(hx --health |tail -n '+7' |awk '{print $1}' |sed 's/\x1b\[[0-9;]*m//g') 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 esac
} && complete -o filenames -F _hx hx
local IFS=$'\n'
COMPREPLY=($COMPREPLY)
} && complete -o filenames -F _hx hx

@ -48,7 +48,7 @@ encoding_rs = "0.8"
chrono = { version = "0.4", default-features = false, features = ["alloc", "std"] } chrono = { version = "0.4", default-features = false, features = ["alloc", "std"] }
etcetera = "0.8" etcetera = "0.8"
textwrap = "0.16.0" textwrap = "0.16.1"
nucleo.workspace = true nucleo.workspace = true
parking_lot = "0.12" parking_lot = "0.12"

@ -53,7 +53,7 @@ fn prioritize_runtime_dirs() -> Vec<PathBuf> {
rt_dirs.push(conf_rt_dir); rt_dirs.push(conf_rt_dir);
if let Ok(dir) = std::env::var("HELIX_RUNTIME") { 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)); rt_dirs.push(path::normalize(dir));
} }

@ -1017,7 +1017,7 @@ impl Client {
pub fn resolve_completion_item( pub fn resolve_completion_item(
&self, &self,
completion_item: lsp::CompletionItem, completion_item: lsp::CompletionItem,
) -> Option<impl Future<Output = Result<Value>>> { ) -> Option<impl Future<Output = Result<lsp::CompletionItem>>> {
let capabilities = self.capabilities.get().unwrap(); let capabilities = self.capabilities.get().unwrap();
// Return early if the server does not support resolving completion items. // Return early if the server does not support resolving completion items.
@ -1029,7 +1029,8 @@ impl Client {
_ => return None, _ => return None,
} }
Some(self.call::<lsp::request::ResolveCompletionItem>(completion_item)) let res = self.call::<lsp::request::ResolveCompletionItem>(completion_item);
Some(async move { Ok(serde_json::from_value(res.await?)?) })
} }
pub fn resolve_code_action( pub fn resolve_code_action(

@ -1,6 +1,9 @@
pub use etcetera::home_dir; 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; 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 /// 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 /// unchanged. The tilde will only be expanded when present as the first component of the path
/// and only slash follows it. /// and only slash follows it.
pub fn expand_tilde(path: impl AsRef<Path>) -> PathBuf { pub fn expand_tilde<'a, P>(path: P) -> Cow<'a, Path>
let path = path.as_ref(); where
let mut components = path.components().peekable(); P: Into<Cow<'a, Path>>,
if let Some(Component::Normal(c)) = components.peek() { {
if c == &"~" { let path = path.into();
if let Ok(home) = home_dir() { let mut components = path.components();
// it's ok to unwrap, the path starts with `~` if let Some(Component::Normal(c)) = components.next() {
return home.join(path.strip_prefix("~").unwrap()); 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. /// Normalize a path without resolving symlinks.
@ -109,9 +115,9 @@ pub fn normalize(path: impl AsRef<Path>) -> PathBuf {
/// This function is used instead of [`std::fs::canonicalize`] because we don't want to verify /// 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. /// here if the path exists, just normalize it's components.
pub fn canonicalize(path: impl AsRef<Path>) -> PathBuf { pub fn canonicalize(path: impl AsRef<Path>) -> PathBuf {
let path = expand_tilde(path); let path = expand_tilde(path.as_ref());
let path = if path.is_relative() { let path = if path.is_relative() {
current_working_dir().join(path) Cow::Owned(current_working_dir().join(path))
} else { } else {
path path
}; };
@ -183,3 +189,32 @@ pub fn get_truncated_path(path: impl AsRef<Path>) -> PathBuf {
ret.push(file); ret.push(file);
ret 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);
}
}
}

@ -277,6 +277,10 @@ impl MappableCommand {
page_down, "Move page down", page_down, "Move page down",
half_page_up, "Move half page up", half_page_up, "Move half page up",
half_page_down, "Move half page down", 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_all, "Select whole document",
select_regex, "Select all regex matches inside selections", select_regex, "Select all regex matches inside selections",
split_selection, "Split selections on regex matches", 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::*; use Direction::*;
let config = cx.editor.config(); let config = cx.editor.config();
let (view, doc) = current!(cx.editor); 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 doc_text = doc.text().slice(..);
let viewport = view.inner_area(doc); let viewport = view.inner_area(doc);
let text_fmt = doc.text_format(viewport.width, None); 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( (view.offset.anchor, view.offset.vertical_offset) = char_idx_at_visual_offset(
doc_text, doc_text,
view.offset.anchor, view.offset.anchor,
@ -1639,6 +1643,30 @@ pub fn scroll(cx: &mut Context, offset: usize, direction: Direction) {
&annotations, &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; let mut head;
match direction { match direction {
Forward => { Forward => {
@ -1689,25 +1717,49 @@ pub fn scroll(cx: &mut Context, offset: usize, direction: Direction) {
fn page_up(cx: &mut Context) { fn page_up(cx: &mut Context) {
let view = view!(cx.editor); let view = view!(cx.editor);
let offset = view.inner_height(); let offset = view.inner_height();
scroll(cx, offset, Direction::Backward); scroll(cx, offset, Direction::Backward, false);
} }
fn page_down(cx: &mut Context) { fn page_down(cx: &mut Context) {
let view = view!(cx.editor); let view = view!(cx.editor);
let offset = view.inner_height(); let offset = view.inner_height();
scroll(cx, offset, Direction::Forward); scroll(cx, offset, Direction::Forward, false);
} }
fn half_page_up(cx: &mut Context) { fn half_page_up(cx: &mut Context) {
let view = view!(cx.editor); let view = view!(cx.editor);
let offset = view.inner_height() / 2; let offset = view.inner_height() / 2;
scroll(cx, offset, Direction::Backward); scroll(cx, offset, Direction::Backward, false);
} }
fn half_page_down(cx: &mut Context) { fn half_page_down(cx: &mut Context) {
let view = view!(cx.editor); let view = view!(cx.editor);
let offset = view.inner_height() / 2; 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)] #[allow(deprecated)]
@ -4875,11 +4927,11 @@ fn align_view_middle(cx: &mut Context) {
} }
fn scroll_up(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) { 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) { fn goto_ts_object_impl(cx: &mut Context, object: &'static str, direction: Direction) {

@ -110,14 +110,14 @@ fn open(cx: &mut compositor::Context, args: &[Cow<str>], event: PromptEvent) ->
ensure!(!args.is_empty(), "wrong argument count"); ensure!(!args.is_empty(), "wrong argument count");
for arg in args { for arg in args {
let (path, pos) = args::parse_file(arg); 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 // If the path is a directory, open a file picker on that directory and update the status
// message // message
if let Ok(true) = std::fs::canonicalize(&path).map(|p| p.is_dir()) { if let Ok(true) = std::fs::canonicalize(&path).map(|p| p.is_dir()) {
let callback = async move { let callback = async move {
let call: job::Callback = job::Callback::EditorCompositor(Box::new( let call: job::Callback = job::Callback::EditorCompositor(Box::new(
move |editor: &mut Editor, compositor: &mut Compositor| { 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))); compositor.push(Box::new(overlaid(picker)));
}, },
)); ));
@ -1078,11 +1078,11 @@ fn change_current_directory(
return Ok(()); return Ok(());
} }
let dir = helix_stdx::path::expand_tilde( let dir = args
args.first() .first()
.context("target directory not provided")? .context("target directory not provided")?
.as_ref(), .as_ref();
); let dir = helix_stdx::path::expand_tilde(Path::new(dir));
helix_stdx::env::set_current_working_dir(dir)?; helix_stdx::env::set_current_working_dir(dir)?;

@ -221,9 +221,17 @@ fn request_completion(
.iter() .iter()
.find(|&trigger| trigger_text.ends_with(trigger)) .find(|&trigger| trigger_text.ends_with(trigger))
}); });
lsp::CompletionContext {
trigger_kind: lsp::CompletionTriggerKind::TRIGGER_CHARACTER, if trigger_char.is_some() {
trigger_character: trigger_char.cloned(), lsp::CompletionContext {
trigger_kind: lsp::CompletionTriggerKind::TRIGGER_CHARACTER,
trigger_character: trigger_char.cloned(),
}
} else {
lsp::CompletionContext {
trigger_kind: lsp::CompletionTriggerKind::INVOKED,
trigger_character: None,
}
} }
}; };

@ -178,8 +178,8 @@ pub fn default() -> HashMap<Mode, KeyTrie> {
"esc" => normal_mode, "esc" => normal_mode,
"C-b" | "pageup" => page_up, "C-b" | "pageup" => page_up,
"C-f" | "pagedown" => page_down, "C-f" | "pagedown" => page_down,
"C-u" => half_page_up, "C-u" => page_cursor_half_up,
"C-d" => half_page_down, "C-d" => page_cursor_half_down,
"C-w" => { "Window" "C-w" => { "Window"
"C-w" | "w" => rotate_view, "C-w" | "w" => rotate_view,
@ -287,8 +287,8 @@ pub fn default() -> HashMap<Mode, KeyTrie> {
"j" | "down" => scroll_down, "j" | "down" => scroll_down,
"C-b" | "pageup" => page_up, "C-b" | "pageup" => page_up,
"C-f" | "pagedown" => page_down, "C-f" | "pagedown" => page_down,
"C-u" | "backspace" => half_page_up, "C-u" | "backspace" => page_cursor_half_up,
"C-d" | "space" => half_page_down, "C-d" | "space" => page_cursor_half_down,
"/" => search, "/" => search,
"?" => rsearch, "?" => rsearch,
@ -304,8 +304,8 @@ pub fn default() -> HashMap<Mode, KeyTrie> {
"j" | "down" => scroll_down, "j" | "down" => scroll_down,
"C-b" | "pageup" => page_up, "C-b" | "pageup" => page_up,
"C-f" | "pagedown" => page_down, "C-f" | "pagedown" => page_down,
"C-u" | "backspace" => half_page_up, "C-u" | "backspace" => page_cursor_half_up,
"C-d" | "space" => half_page_down, "C-d" | "space" => page_cursor_half_down,
"/" => search, "/" => search,
"?" => rsearch, "?" => rsearch,

@ -1,7 +1,9 @@
use crate::{ use crate::{
compositor::{Component, Context, Event, EventResult}, compositor::{Component, Context, Event, EventResult},
handlers::trigger_auto_completion, handlers::trigger_auto_completion,
job,
}; };
use helix_event::AsyncHook;
use helix_view::{ use helix_view::{
document::SavePoint, document::SavePoint,
editor::CompleteAction, editor::CompleteAction,
@ -10,14 +12,14 @@ use helix_view::{
theme::{Modifier, Style}, theme::{Modifier, Style},
ViewId, ViewId,
}; };
use tokio::time::Instant;
use tui::{buffer::Buffer as Surface, text::Span}; 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_core::{chars, Change, Transaction};
use helix_view::{graphics::Rect, Document, Editor}; use helix_view::{graphics::Rect, Document, Editor};
use crate::commands;
use crate::ui::{menu, Markdown, Menu, Popup, PromptEvent}; use crate::ui::{menu, Markdown, Menu, Popup, PromptEvent};
use helix_lsp::{lsp, util, OffsetEncoding}; use helix_lsp::{lsp, util, OffsetEncoding};
@ -102,6 +104,7 @@ pub struct Completion {
#[allow(dead_code)] #[allow(dead_code)]
trigger_offset: usize, trigger_offset: usize,
filter: String, filter: String,
resolve_handler: tokio::sync::mpsc::Sender<CompletionItem>,
} }
impl Completion { impl Completion {
@ -368,6 +371,7 @@ impl Completion {
// TODO: expand nucleo api to allow moving straight to a Utf32String here // TODO: expand nucleo api to allow moving straight to a Utf32String here
// and avoid allocation during matching // and avoid allocation during matching
filter: String::from(fragment), filter: String::from(fragment),
resolve_handler: ResolveHandler::default().spawn(),
}; };
// need to recompute immediately in case start_offset != trigger_offset // need to recompute immediately in case start_offset != trigger_offset
@ -379,6 +383,8 @@ impl Completion {
completion completion
} }
/// Synchronously resolve the given completion item. This is used when
/// accepting a completion.
fn resolve_completion_item( fn resolve_completion_item(
language_server: &helix_lsp::Client, language_server: &helix_lsp::Client,
completion_item: lsp::CompletionItem, completion_item: lsp::CompletionItem,
@ -386,7 +392,7 @@ impl Completion {
let future = language_server.resolve_completion_item(completion_item)?; let future = language_server.resolve_completion_item(completion_item)?;
let response = helix_lsp::block_on(future); let response = helix_lsp::block_on(future);
match response { match response {
Ok(value) => serde_json::from_value(value).ok(), Ok(item) => Some(item),
Err(err) => { Err(err) => {
log::error!("Failed to resolve completion item: {}", err); log::error!("Failed to resolve completion item: {}", err);
None None
@ -420,62 +426,6 @@ impl Completion {
self.popup.contents_mut().replace_option(old_item, new_item); 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<lsp::CompletionItem>| {
let resolved_item = match response {
Some(item) => item,
None => return,
};
if let Some(completion) = &mut compositor
.find::<crate::ui::EditorView>()
.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 { pub fn area(&mut self, viewport: Rect, editor: &Editor) -> Rect {
self.popup.area(viewport, editor) self.popup.area(viewport, editor)
} }
@ -498,6 +448,9 @@ impl Component for Completion {
Some(option) => option, Some(option) => option,
None => return, None => return,
}; };
if !option.resolved {
helix_event::send_blocking(&self.resolve_handler, option.clone());
}
// need to render: // need to render:
// option.detail // option.detail
// --- // ---
@ -599,3 +552,88 @@ impl Component for Completion {
markdown_doc.render(doc_area, surface, cx); 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<CompletionItem>,
request: Option<helix_event::CancelTx>,
}
impl AsyncHook for ResolveHandler {
type Event = CompletionItem;
fn handle_event(
&mut self,
item: Self::Event,
timeout: Option<tokio::time::Instant>,
) -> Option<tokio::time::Instant> {
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::<crate::ui::EditorView>()
.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 => (),
}
});
}

@ -1027,14 +1027,6 @@ impl EditorView {
pub fn handle_idle_timeout(&mut self, cx: &mut commands::Context) -> EventResult { pub fn handle_idle_timeout(&mut self, cx: &mut commands::Context) -> EventResult {
commands::compute_inlay_hints_for_all_views(cx.editor, cx.jobs); 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) EventResult::Ignored(None)
} }
} }
@ -1088,6 +1080,15 @@ impl EditorView {
if modifiers == KeyModifiers::ALT { if modifiers == KeyModifiers::ALT {
let selection = doc.selection(view_id).clone(); let selection = doc.selection(view_id).clone();
doc.set_selection(view_id, selection.push(Range::point(pos))); 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 { } else {
doc.set_selection(view_id, Selection::point(pos)); doc.set_selection(view_id, Selection::point(pos));
} }
@ -1156,7 +1157,7 @@ impl EditorView {
} }
let offset = config.scroll_lines.unsigned_abs(); 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.tree.focus = current_view;
cxt.editor.ensure_cursor_in_view(current_view); cxt.editor.ensure_cursor_in_view(current_view);
@ -1171,19 +1172,26 @@ impl EditorView {
let (view, doc) = current!(cxt.editor); let (view, doc) = current!(cxt.editor);
if doc let should_yank = match cxt.editor.mouse_down_range.take() {
.selection(view.id) Some(down_range) => doc.selection(view.id).primary() != down_range,
.primary() None => {
.slice(doc.text().slice(..)) // This should not happen under normal cases. We fall back to the original
.len_chars() // behavior of yanking on non-single-char selections.
<= 1 doc.selection(view.id)
{ .primary()
return EventResult::Ignored(None); .slice(doc.text().slice(..))
} .len_chars()
> 1
commands::MappableCommand::yank_main_selection_to_primary_clipboard.execute(cxt); }
};
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) => { MouseEventKind::Up(MouseButton::Right) => {

@ -428,9 +428,9 @@ pub mod completers {
path path
} else { } else {
match path.parent() { 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("")... // Path::new("h")'s parent is Some("")...
_ => helix_stdx::env::current_working_dir(), _ => Cow::Owned(helix_stdx::env::current_working_dir()),
} }
}; };

@ -42,7 +42,7 @@ pub use helix_core::diagnostic::Severity;
use helix_core::{ use helix_core::{
auto_pairs::AutoPairs, auto_pairs::AutoPairs,
syntax::{self, AutoPairConfig, IndentationHeuristic, LanguageServerFeature, SoftWrap}, 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_dap as dap;
use helix_lsp::lsp; use helix_lsp::lsp;
@ -964,6 +964,8 @@ pub struct Editor {
/// times during rendering and should not be set by other functions. /// times during rendering and should not be set by other functions.
pub cursor_cache: Cell<Option<Option<Position>>>, pub cursor_cache: Cell<Option<Option<Position>>>,
pub handlers: Handlers, pub handlers: Handlers,
pub mouse_down_range: Option<Range>,
} }
pub type Motion = Box<dyn Fn(&mut Editor)>; pub type Motion = Box<dyn Fn(&mut Editor)>;
@ -1080,6 +1082,7 @@ impl Editor {
needs_redraw: false, needs_redraw: false,
cursor_cache: Cell::new(None), cursor_cache: Cell::new(None),
handlers, handlers,
mouse_down_range: None,
} }
} }
@ -1978,7 +1981,7 @@ impl Editor {
/// Switches the editor into normal mode. /// Switches the editor into normal mode.
pub fn enter_normal_mode(&mut self) { pub fn enter_normal_mode(&mut self) {
use helix_core::{graphemes, Range}; use helix_core::graphemes;
if self.mode == Mode::Normal { if self.mode == Mode::Normal {
return; return;

@ -226,10 +226,15 @@ impl Editor {
breakpoints.iter().position(|b| b.id == breakpoint.id) breakpoints.iter().position(|b| b.id == breakpoint.id)
{ {
breakpoints[i].verified = breakpoint.verified; breakpoints[i].verified = breakpoint.verified;
breakpoints[i].message = breakpoint.message.clone(); breakpoints[i].message = breakpoint
breakpoints[i].line = .message
breakpoint.line.unwrap().saturating_sub(1); // TODO: no unwrap .clone()
breakpoints[i].column = breakpoint.column; .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);
} }
} }
} }

@ -23,6 +23,7 @@ cuelsp = { command = "cuelsp" }
dart = { command = "dart", args = ["language-server", "--client-id=helix"] } dart = { command = "dart", args = ["language-server", "--client-id=helix"] }
dhall-lsp-server = { command = "dhall-lsp-server" } dhall-lsp-server = { command = "dhall-lsp-server" }
docker-langserver = { command = "docker-langserver", args = ["--stdio"] } 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"] } dot-language-server = { command = "dot-language-server", args = ["--stdio"] }
elixir-ls = { command = "elixir-ls", config = { elixirLS.dialyzerEnabled = false } } elixir-ls = { command = "elixir-ls", config = { elixirLS.dialyzerEnabled = false } }
elm-language-server = { command = "elm-language-server" } elm-language-server = { command = "elm-language-server" }
@ -860,6 +861,7 @@ file-types = [
"tcshrc", "tcshrc",
"bashrc_Apple_Terminal", "bashrc_Apple_Terminal",
"zshrc_Apple_Terminal", "zshrc_Apple_Terminal",
{ glob = "tmux.conf" },
{ glob = ".bash_history" }, { glob = ".bash_history" },
{ glob = ".bash_login" }, { glob = ".bash_login" },
{ glob = ".bash_logout" }, { glob = ".bash_logout" },
@ -1091,7 +1093,7 @@ name = "lua"
injection-regex = "lua" injection-regex = "lua"
scope = "source.lua" scope = "source.lua"
file-types = ["lua"] file-types = ["lua"]
shebangs = ["lua"] shebangs = ["lua", "luajit"]
roots = [".luarc.json", ".luacheckrc", ".stylua.toml", "selene.toml", ".git"] roots = [".luarc.json", ".luacheckrc", ".stylua.toml", "selene.toml", ".git"]
comment-token = "--" comment-token = "--"
indent = { tab-width = 2, unit = " " } indent = { tab-width = 2, unit = " " }
@ -1459,6 +1461,16 @@ language-servers = [ "docker-langserver" ]
name = "dockerfile" name = "dockerfile"
source = { git = "https://github.com/camdencheek/tree-sitter-dockerfile", rev = "8ee3a0f7587b2bd8c45c8cb7d28bd414604aec62" } 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]] [[language]]
name = "git-commit" name = "git-commit"
scope = "git.commitmsg" scope = "git.commitmsg"
@ -1813,7 +1825,7 @@ injection-regex = "sql"
[[grammar]] [[grammar]]
name = "sql" 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]] [[language]]
name = "gdscript" name = "gdscript"
@ -2359,6 +2371,8 @@ file-types = [
"menu", "menu",
"mxml", "mxml",
"nuspec", "nuspec",
"osc",
"osm",
"pt", "pt",
"publishsettings", "publishsettings",
"pubxml", "pubxml",
@ -3113,3 +3127,16 @@ indent = { tab-width = 2, unit = " " }
[[grammar]] [[grammar]]
name = "pkl" name = "pkl"
source = { git = "https://github.com/apple/tree-sitter-pkl", rev = "c03f04a313b712f8ab00a2d862c10b37318699ae" } 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" }

@ -56,9 +56,34 @@
(documentation_comment)+ @comment.around (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 (expression_statement
((identifier) @_name (#any-of? @_name "test" "testWidgets")) ((identifier) @_name (#any-of? @_name "test" "testWidgets"))

@ -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

@ -0,0 +1,2 @@
([(line_comment) (block_comment)] @injection.content
(#set! injection.language "comment"))

@ -0,0 +1,6 @@
(comment) @comment.inside
(comment)+ @comment.around
(function_arguments
((_) @parameter.inside . ","? @parameter.around) @parameter.around)

@ -0,0 +1,9 @@
(comment) @comment.inside
(comment)+ @comment.around
(formals
((_) @parameter.inside . ","? @parameter.around) @parameter.around)
(function_expression
body: (_) @function.inside) @function.around

@ -34,6 +34,9 @@
(arguments (arguments
((_) @parameter.inside . ","? @parameter.around) @parameter.around) ((_) @parameter.inside . ","? @parameter.around) @parameter.around)
(field_initializer_list
((_) @parameter.inside . ","? @parameter.around) @parameter.around)
[ [
(line_comment) (line_comment)
(block_comment) (block_comment)

@ -24,20 +24,20 @@
(term (term
alias: (identifier) @variable.parameter) alias: (identifier) @variable.parameter)
(term ((term
value: (cast value: (cast
name: (keyword_cast) @function.builtin name: (keyword_cast) @function.builtin
parameter: [(literal)]?)) parameter: [(literal)]?)))
(literal) @string (literal) @string
(comment) @comment.line (comment) @comment.line
(marginalia) @comment.block (marginalia) @comment.block
((literal) @constant.numeric.integer ((literal) @constant.numeric.integer
(#match? @constant.numeric.integer "^-?\\d+$")) (#match? @constant.numeric.integer "^[-+]?\\d+$"))
((literal) @constant.numeric.float ((literal) @constant.numeric.float
(#match? @constant.numeric.float "^-?\\d*\\.\\d*$")) (#match? @constant.numeric.float "^[-+]?\\d*\\.\\d*$"))
(parameter) @variable.parameter (parameter) @variable.parameter

@ -1,6 +1,10 @@
# An approximation/port of the Cyan Light Theme from Jetbrains # Cyan Light
# # Adapted from JetBrains' Cyan Light Theme https://plugins.jetbrains.com/plugin/12102-cyan-light-theme
# Original Color Scheme here https://plugins.jetbrains.com/plugin/12102-cyan-light-theme # Author: Abderrahmane Tahri Jouti <tj.abderrahmane@gmail.com>
# Original Author : Olga Berdnikova
# LICENSE : MIT
# Source: https://github.com/OlyaB/CyanTheme
"attribute" = "blue" "attribute" = "blue"
"type" = "shade07" "type" = "shade07"

@ -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.

@ -0,0 +1,120 @@
# Monokai Soda port for Helix (https://helix-editor.com)
# Author : Jimmy Zelinskie <jimmy@zelinskie.com>
# 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"

@ -6,24 +6,23 @@
# License: MIT License # License: MIT License
"type" = "blue" "type" = "blue"
"constant" = "purple" "constant" = "fg"
"constant.numeric" = "purple" "constant.numeric" = "purple"
"constant.character.escape" = "orange" "constant.character.escape" = "orange"
"string" = "yellow" "string" = "yellow"
"comment" = "grey" "comment" = "grey"
"variable" = "fg" "variable" = "fg"
"variable.builtin" = "orange" "variable.builtin" = "purple"
"variable.parameter" = "fg" "variable.parameter" = "fg"
"variable.other.member" = "fg" "variable.other.member" = "orange"
"label" = "orange" "label" = "red"
"punctuation" = "grey" "punctuation" = "grey"
"punctuation.delimiter" = "grey" "punctuation.special" = "yellow"
"punctuation.bracket" = "fg"
"keyword" = "red" "keyword" = "red"
"operator" = "orange" "operator" = "red"
"function" = "green" "function" = "green"
"function.builtin" = "blue" "function.builtin" = "green"
"function.macro" = "purple" "function.macro" = "green"
"tag" = "yellow" "tag" = "yellow"
"namespace" = "blue" "namespace" = "blue"
"attribute" = "purple" "attribute" = "purple"
@ -48,12 +47,12 @@
"markup.raw" = "green" "markup.raw" = "green"
"diff.plus" = "green" "diff.plus" = "green"
"diff.delta" = "orange" "diff.delta" = "blue"
"diff.minus" = "red" "diff.minus" = "red"
"ui.background" = { bg = "bg0" } "ui.background" = { bg = "bg0" }
"ui.cursor" = { modifiers = ['reversed'] } "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.insert" = { fg = "black", bg = "grey" }
"ui.cursor.select" = { fg = "bg0", bg = "blue" } "ui.cursor.select" = { fg = "bg0", bg = "blue" }
"ui.selection" = { bg = "bg5" } "ui.selection" = { bg = "bg5" }
@ -73,7 +72,7 @@
"ui.text.focus" = "green" "ui.text.focus" = "green"
"ui.menu" = { fg = "fg", bg = "bg2" } "ui.menu" = { fg = "fg", bg = "bg2" }
"ui.menu.selected" = { fg = "bg0", bg = "green" } "ui.menu.selected" = { fg = "bg0", bg = "green" }
"ui.virtual.whitespace" = { fg = "grey_dim" } "ui.virtual.whitespace" = "bg4"
"ui.virtual.ruler" = { bg = "bg3" } "ui.virtual.ruler" = { bg = "bg3" }
"ui.virtual.inlay-hint" = { fg = "grey_dim" } "ui.virtual.inlay-hint" = { fg = "grey_dim" }
@ -92,11 +91,12 @@ error = { fg = 'red', bg = 'bg2', modifiers = ['bold'] }
[palette] [palette]
black = "#181819" black = "#181819"
bg_dim = "#222327"
bg0 = "#2c2e34" bg0 = "#2c2e34"
bg1 = "#33353f" bg1 = "#33353f"
bg2 = "#363944" bg2 = "#363944"
bg3 = "#3b3e48" bg3 = "#3b3e48"
bg4 = "#5C606A" bg4 = "#414550"
bg5 = "#444852" bg5 = "#444852"
bg_red = "#ff6077" bg_red = "#ff6077"
diff_red = "#55393d" diff_red = "#55393d"

Loading…
Cancel
Save