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]]
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",

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

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

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

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

@ -53,7 +53,7 @@ fn prioritize_runtime_dirs() -> Vec<PathBuf> {
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));
}

@ -1017,7 +1017,7 @@ impl Client {
pub fn resolve_completion_item(
&self,
completion_item: lsp::CompletionItem,
) -> Option<impl Future<Output = Result<Value>>> {
) -> Option<impl Future<Output = Result<lsp::CompletionItem>>> {
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::<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(

@ -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<Path>) -> 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<Cow<'a, Path>>,
{
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<Path>) -> 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<Path>) -> 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<Path>) -> 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);
}
}
}

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

@ -110,14 +110,14 @@ fn open(cx: &mut compositor::Context, args: &[Cow<str>], 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)?;

@ -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,
}
}
};

@ -178,8 +178,8 @@ pub fn default() -> HashMap<Mode, KeyTrie> {
"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<Mode, KeyTrie> {
"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<Mode, KeyTrie> {
"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,

@ -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<CompletionItem>,
}
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<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 {
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<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 {
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) => {

@ -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()),
}
};

@ -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<Option<Option<Position>>>,
pub handlers: Handlers,
pub mouse_down_range: Option<Range>,
}
pub type Motion = Box<dyn Fn(&mut Editor)>;
@ -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;

@ -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);
}
}
}

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

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

@ -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
((_) @parameter.inside . ","? @parameter.around) @parameter.around)
(field_initializer_list
((_) @parameter.inside . ","? @parameter.around) @parameter.around)
[
(line_comment)
(block_comment)

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

@ -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 <tj.abderrahmane@gmail.com>
# Original Author : Olga Berdnikova
# LICENSE : MIT
# Source: https://github.com/OlyaB/CyanTheme
"attribute" = "blue"
"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
"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"

Loading…
Cancel
Save