Merge branch 'master' into debug

This commit is contained in:
Blaž Hrastnik 2021-10-17 13:51:56 +09:00
commit 0a6b60085a
31 changed files with 846 additions and 117 deletions

8
.gitmodules vendored
View file

@ -122,3 +122,11 @@
path = helix-syntax/languages/tree-sitter-svelte
url = https://github.com/Himujjal/tree-sitter-svelte
shallow = true
[submodule "helix-syntax/languages/tree-sitter-vue"]
path = helix-syntax/languages/tree-sitter-vue
url = https://github.com/ikatyang/tree-sitter-vue
shallow = true
[submodule "helix-syntax/languages/tree-sitter-tsq"]
path = helix-syntax/languages/tree-sitter-tsq
url = https://github.com/tree-sitter/tree-sitter-tsq
shallow = true

69
Cargo.lock generated
View file

@ -37,9 +37,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bstr"
version = "0.2.16"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90682c8d613ad3373e66de8c6411e0ae2ab2571e879d2efbf73558cc66f21279"
checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
dependencies = [
"lazy_static",
"memchr",
@ -54,9 +54,9 @@ checksum = "72feb31ffc86498dacdbd0fcebb56138e7177a8cc5cea4516031d15ae85a742e"
[[package]]
name = "bytes"
version = "1.0.1"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040"
checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8"
[[package]]
name = "cassowary"
@ -66,9 +66,9 @@ checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
[[package]]
name = "cc"
version = "1.0.70"
version = "1.0.71"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d26a6ce4b6a484fa3edb70f7efa6fc430fd2b87285fe8b84304fd0936faa0dc0"
checksum = "79c2681d6594606957bbb8631c4b90a7fcaaa72cdb714743a437b156d6a7eedd"
[[package]]
name = "cfg-if"
@ -369,6 +369,7 @@ dependencies = [
"regex",
"ropey",
"serde",
"serde_json",
"similar",
"smallvec",
"tendril",
@ -531,18 +532,18 @@ dependencies = [
[[package]]
name = "instant"
version = "0.1.10"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bee0328b1209d157ef001c94dd85b4f8f64139adb0eac2659f4b08382b2f474d"
checksum = "716d3d89f35ac6a34fd0eed635395f4c3b76fa889338a4632e5231a8684216bd"
dependencies = [
"cfg-if",
]
[[package]]
name = "itoa"
version = "0.4.7"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
[[package]]
name = "jsonrpc-core"
@ -565,15 +566,15 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.99"
version = "0.2.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7f823d141fe0a24df1e23b4af4e3c7ba9e5966ec514ea068c93024aa7deb765"
checksum = "dd8f7255a17a627354f321ef0055d63b898c6fb27eff628af4d1b66b7331edf6"
[[package]]
name = "libloading"
version = "0.7.0"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f84d96438c15fcd6c3f244c8fce01d1e2b9c6b5623e9c711dc9286d8fc92d6a"
checksum = "c0cf036d15402bea3c5d4de17b3fce76b3e4a56ebc1f577be0e7a72f7c607cf0"
dependencies = [
"cfg-if",
"winapi",
@ -581,9 +582,9 @@ dependencies = [
[[package]]
name = "lock_api"
version = "0.4.4"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0382880606dff6d15c9476c416d18690b72742aa7b605bb6dd6ec9030fbf07eb"
checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109"
dependencies = [
"scopeguard",
]
@ -599,9 +600,9 @@ dependencies = [
[[package]]
name = "lsp-types"
version = "0.90.0"
version = "0.90.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7404037aab080771c90b0a499836d9d8a10336ecd07badf969567b65c6d51a1"
checksum = "6f3734ab1d7d157fc0c45110e06b587c31cd82bea2ccfd6b563cbff0aaeeb1d3"
dependencies = [
"bitflags",
"serde",
@ -711,9 +712,9 @@ checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
[[package]]
name = "parking_lot"
version = "0.11.1"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb"
checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
dependencies = [
"instant",
"lock_api",
@ -722,9 +723,9 @@ dependencies = [
[[package]]
name = "parking_lot_core"
version = "0.8.3"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018"
checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216"
dependencies = [
"cfg-if",
"instant",
@ -754,9 +755,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "proc-macro2"
version = "1.0.28"
version = "1.0.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c7ed8b8c7b886ea3ed7dde405212185f423ab44682667c8c6dd14aa1d9f6612"
checksum = "b9f5105d4fdaab20335ca9565e106a5d9b82b6219b5ba735731124ac6711d23d"
dependencies = [
"unicode-xid",
]
@ -999,9 +1000,9 @@ checksum = "d44a3643b4ff9caf57abcee9c2c621d6c03d9135e0d8b589bd9afb5992cb176a"
[[package]]
name = "syn"
version = "1.0.74"
version = "1.0.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1873d832550d4588c3dbc20f01361ab00bfe741048f71e3fecf145a7cc18b29c"
checksum = "a4eac2e6c19f5c3abc0c229bea31ff0b9b091c7b14990e8924b92902a303a0c0"
dependencies = [
"proc-macro2",
"quote",
@ -1021,18 +1022,18 @@ dependencies = [
[[package]]
name = "thiserror"
version = "1.0.29"
version = "1.0.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "602eca064b2d83369e2b2f34b09c70b605402801927c65c11071ac911d299b88"
checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.29"
version = "1.0.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bad553cc2c78e8de258400763a647e80e6d1b31ee237275d756f6836d204494c"
checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b"
dependencies = [
"proc-macro2",
"quote",
@ -1059,9 +1060,9 @@ dependencies = [
[[package]]
name = "tinyvec"
version = "1.3.1"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "848a1e1181b9f6753b5e96a092749e29b11d19ede67dfbbd6c7dc7e0f49b5338"
checksum = "f83b2a3d4d9091d0abd7eba4dc2710b1718583bd4d8992e2190720ea38f391f7"
dependencies = [
"tinyvec_macros",
]
@ -1094,9 +1095,9 @@ dependencies = [
[[package]]
name = "tokio-macros"
version = "1.3.0"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54473be61f4ebe4efd09cec9bd5d16fa51d70ea0192213d754d2d500457db110"
checksum = "154794c8f499c2619acd19e839294703e9e32e7630ef5f46ea80d4ef0fbee5eb"
dependencies = [
"proc-macro2",
"quote",

View file

@ -83,4 +83,6 @@ a good overview of the internals.
# Getting help
Your question might already be answered on the [FAQ](https://github.com/helix-editor/helix/wiki/FAQ).
Discuss the project on the community [Matrix Space](https://matrix.to/#/#helix-community:matrix.org) (make sure to join `#helix-editor:matrix.org` if you're on a client that doesn't support Matrix Spaces yet).

View file

@ -19,6 +19,8 @@ To override global configuration parameters, create a `config.toml` file located
| `line-number` | Line number display (`absolute`, `relative`) | `absolute` |
| `smart-case` | Enable smart case regex searching (case insensitive unless pattern contains upper case characters) | `true` |
| `auto-pairs` | Enable automatic insertion of pairs to parenthese, brackets, etc. | `true` |
| `auto-completion` | Enable automatic pop up of auto-completion. | `true` |
| `idle-timeout` | Time in milliseconds since last keypress before idle timers trigger. Used for autocompletion, set to 0 for instant. | `400` |
## LSP

View file

@ -196,7 +196,7 @@ a > .hljs {
border-radius: 3px;
}
:not(pre):not(a) > .hljs {
:not(pre):not(a):not(td):not(p) > .hljs {
color: var(--inline-code-color);
overflow-x: initial;
}

View file

@ -162,7 +162,6 @@ table thead td {
table thead th {
padding: .75rem;
text-align: left;
color: var(--table-border-color);
font-weight: 500;
line-height: 1.5;
width: auto;
@ -228,3 +227,7 @@ blockquote *:last-child {
margin: 5px 0px;
font-weight: bold;
}
.result-no-output {
font-style: italic;
}

View file

@ -13,7 +13,6 @@
.ayu {
--bg: hsl(210, 25%, 8%);
--fg: #c5c5c5;
--heading-fg: #c5c5c5;
--sidebar-bg: #14191f;
--sidebar-fg: #c8c9db;
@ -54,7 +53,6 @@
.coal {
--bg: hsl(200, 7%, 8%);
--fg: #98a3ad;
--heading-fg: #98a3ad;
--sidebar-bg: #292c2f;
--sidebar-fg: #a1adb8;
@ -95,7 +93,6 @@
.light {
--bg: hsl(0, 0%, 100%);
--fg: hsl(0, 0%, 0%);
--heading-fg: hsl(0, 0%, 0%);
--sidebar-bg: #fafafa;
--sidebar-fg: hsl(0, 0%, 0%);
@ -110,7 +107,7 @@
--links: #20609f;
--inline-code-color: #a39e9b;
--inline-code-color: #301900;
--theme-popup-bg: #fafafa;
--theme-popup-border: #cccccc;
@ -136,7 +133,6 @@
.navy {
--bg: hsl(226, 23%, 11%);
--fg: #bcbdd0;
--heading-fg: #bcbdd0;
--sidebar-bg: #282d3f;
--sidebar-fg: #c8c9db;
@ -177,7 +173,6 @@
.rust {
--bg: hsl(60, 9%, 87%);
--fg: #262625;
--heading-fg: #262625;
--sidebar-bg: #3b2e2a;
--sidebar-fg: #c8c9db;
@ -192,7 +187,7 @@
--links: #2b79a2;
--inline-code-color: #c5c8c6;
--inline-code-color: #6e6b5e;
--theme-popup-bg: #e1e1db;
--theme-popup-border: #b38f6b;
@ -218,8 +213,7 @@
@media (prefers-color-scheme: dark) {
.light.no-js {
--bg: hsl(200, 7%, 8%);
--fg: #ebeafa;
--heading-fg: #ebeafa;
--fg: #98a3ad;
--sidebar-bg: #292c2f;
--sidebar-fg: #a1adb8;
@ -234,7 +228,7 @@
--links: #2b79a2;
--inline-code-color: #6e6b5e;
--inline-code-color: #c5c8c6;
--theme-popup-bg: #141617;
--theme-popup-border: #43484d;

View file

@ -29,6 +29,7 @@ arc-swap = "1"
regex = "1"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
toml = "0.5"
similar = "2.1"

View file

@ -29,10 +29,10 @@ use std::borrow::Cow;
/// "(anchor, head)", followed by example text with "[" and "]"
/// inserted to represent the anchor and head positions:
///
/// - (0, 3): [Som]e text.
/// - (3, 0): ]Som[e text.
/// - (2, 7): So[me te]xt.
/// - (1, 1): S[]ome text.
/// - (0, 3): `[Som]e text`.
/// - (3, 0): `]Som[e text`.
/// - (2, 7): `So[me te]xt`.
/// - (1, 1): `S[]ome text`.
///
/// Ranges are considered to be inclusive on the left and
/// exclusive on the right, regardless of anchor-head ordering.

View file

@ -31,6 +31,15 @@ where
.transpose()
}
fn deserialize_lsp_config<'de, D>(deserializer: D) -> Result<Option<serde_json::Value>, D::Error>
where
D: serde::Deserializer<'de>,
{
Option::<toml::Value>::deserialize(deserializer)?
.map(|toml| toml.try_into().map_err(serde::de::Error::custom))
.transpose()
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Configuration {
pub language: Vec<LanguageConfiguration>,
@ -46,7 +55,9 @@ pub struct LanguageConfiguration {
pub file_types: Vec<String>, // filename ends_with? <Gemfile, rb, etc>
pub roots: Vec<String>, // these indicate project roots <.git, Cargo.toml>
pub comment_token: Option<String>,
pub config: Option<String>,
#[serde(default, skip_serializing, deserialize_with = "deserialize_lsp_config")]
pub config: Option<serde_json::Value>,
#[serde(default)]
pub auto_format: bool,

View file

@ -318,15 +318,7 @@ impl Registry {
let (client, incoming, initialize_notify) = Client::start(
&config.command,
&config.args,
serde_json::from_str(language_config.config.as_deref().unwrap_or(""))
.map_err(|e| {
log::error!(
"LSP Config, {}, in `languages.toml` for `{}`",
e,
language_config.scope()
)
})
.ok(),
language_config.config.clone(),
id,
)?;
self.incoming.push(UnboundedReceiverStream::new(incoming));

@ -0,0 +1 @@
Subproject commit b665659d3238e6036e22ed0e24935e60efb39415

@ -0,0 +1 @@
Subproject commit 91fe2754796cd8fba5f229505a23fa08f3546c06

View file

@ -102,6 +102,7 @@ impl Application {
if !args.files.is_empty() {
let first = &args.files[0]; // we know it's not empty
if first.is_dir() {
std::env::set_current_dir(&first)?;
editor.new_file(Action::VerticalSplit);
compositor.push(Box::new(ui::file_picker(first.clone())));
} else {
@ -204,6 +205,11 @@ impl Application {
self.jobs.handle_callback(&mut self.editor, &mut self.compositor, callback);
self.render();
}
_ = &mut self.editor.idle_timer => {
// idle timeout
self.editor.clear_idle_timer();
self.handle_idle_timeout();
}
}
}
}
@ -233,6 +239,38 @@ impl Application {
}
}
pub fn handle_idle_timeout(&mut self) {
use crate::commands::{completion, Context};
use helix_view::document::Mode;
if doc_mut!(self.editor).mode != Mode::Insert || !self.config.editor.auto_completion {
return;
}
let editor_view = self
.compositor
.find(std::any::type_name::<ui::EditorView>())
.expect("expected at least one EditorView");
let editor_view = editor_view
.as_any_mut()
.downcast_mut::<ui::EditorView>()
.unwrap();
if editor_view.completion.is_some() {
return;
}
let mut cx = Context {
register: None,
editor: &mut self.editor,
jobs: &mut self.jobs,
count: None,
callback: None,
on_next_key_callback: None,
};
completion(&mut cx);
self.render();
}
pub fn handle_terminal_events(&mut self, event: Option<Result<Event, crossterm::ErrorKind>>) {
let mut cx = crate::compositor::Context {
editor: &mut self.editor,
@ -417,14 +455,6 @@ impl Application {
server_id: usize,
) {
use helix_lsp::{Call, MethodCall, Notification};
let editor_view = self
.compositor
.find(std::any::type_name::<ui::EditorView>())
.expect("expected at least one EditorView");
let editor_view = editor_view
.as_any_mut()
.downcast_mut::<ui::EditorView>()
.unwrap();
match call {
Call::Notification(helix_lsp::jsonrpc::Notification { method, params, .. }) => {
@ -534,7 +564,19 @@ impl Application {
Notification::LogMessage(params) => {
log::info!("window/logMessage: {:?}", params);
}
Notification::ProgressMessage(params) => {
Notification::ProgressMessage(params)
if !self
.compositor
.has_component(std::any::type_name::<ui::Prompt>()) =>
{
let editor_view = self
.compositor
.find(std::any::type_name::<ui::EditorView>())
.expect("expected at least one EditorView");
let editor_view = editor_view
.as_any_mut()
.downcast_mut::<ui::EditorView>()
.unwrap();
let lsp::ProgressParams { token, value } = params;
let lsp::ProgressParamsValue::WorkDone(work) = value;
@ -609,6 +651,9 @@ impl Application {
self.editor.set_status(status);
}
}
Notification::ProgressMessage(_params) => {
// do nothing
}
}
}
Call::MethodCall(helix_lsp::jsonrpc::MethodCall {
@ -643,6 +688,14 @@ impl Application {
MethodCall::WorkDoneProgressCreate(params) => {
self.lsp_progress.create(server_id, params.token);
let editor_view = self
.compositor
.find(std::any::type_name::<ui::EditorView>())
.expect("expected at least one EditorView");
let editor_view = editor_view
.as_any_mut()
.downcast_mut::<ui::EditorView>()
.unwrap();
let spinner = editor_view.spinners_mut().get_or_create(server_id);
if spinner.is_stopped() {
spinner.start();

View file

@ -1310,7 +1310,8 @@ fn global_search(cx: &mut Context) {
cx.push_layer(Box::new(prompt));
let root = find_root(None).unwrap_or_else(|| PathBuf::from("./"));
let current_path = doc_mut!(cx.editor).path().cloned();
let show_picker = async move {
let all_matches: Vec<(usize, PathBuf)> =
UnboundedReceiverStream::new(all_matches_rx).collect().await;
@ -1320,14 +1321,19 @@ fn global_search(cx: &mut Context) {
editor.set_status("No matches found".to_string());
return;
}
let picker = FilePicker::new(
all_matches,
move |(_line_num, path)| {
path.strip_prefix(&root)
.unwrap_or(path)
let relative_path = helix_core::path::get_relative_path(path)
.to_str()
.unwrap()
.into()
.to_owned();
if current_path.as_ref().map(|p| p == path).unwrap_or(false) {
format!("{} (*)", relative_path).into()
} else {
relative_path.into()
}
},
move |editor: &mut Editor, (line_num, path), action| {
match editor.open(path.into(), action) {
@ -4160,7 +4166,7 @@ fn remove_primary_selection(cx: &mut Context) {
doc.set_selection(view.id, selection);
}
fn completion(cx: &mut Context) {
pub fn completion(cx: &mut Context) {
// trigger on trigger char, or if user calls it
// (or on word char typing??)
// after it's triggered, if response marked is_incomplete, update on every subsequent keypress
@ -4205,10 +4211,8 @@ fn completion(cx: &mut Context) {
};
let offset_encoding = language_server.offset_encoding();
let cursor = doc
.selection(view.id)
.primary()
.cursor(doc.text().slice(..));
let text = doc.text().slice(..);
let cursor = doc.selection(view.id).primary().cursor(text);
let pos = pos_to_lsp_pos(doc.text(), cursor, offset_encoding);
@ -4216,6 +4220,15 @@ fn completion(cx: &mut Context) {
let trigger_offset = cursor;
// TODO: trigger_offset should be the cursor offset but we also need a starting offset from where we want to apply
// completion filtering. For example logger.te| should filter the initial suggestion list with "te".
use helix_core::chars;
let mut iter = text.chars_at(cursor);
iter.reverse();
let offset = iter.take_while(|ch| chars::char_is_word(*ch)).count();
let start_offset = cursor.saturating_sub(offset);
cx.callback(
future,
move |editor: &mut Editor,
@ -4238,7 +4251,7 @@ fn completion(cx: &mut Context) {
};
if items.is_empty() {
editor.set_error("No completion available".to_string());
// editor.set_error("No completion available".to_string());
return;
}
let size = compositor.size();
@ -4246,7 +4259,14 @@ fn completion(cx: &mut Context) {
.find(std::any::type_name::<ui::EditorView>())
.unwrap();
if let Some(ui) = ui.as_any_mut().downcast_mut::<ui::EditorView>() {
ui.set_completion(items, offset_encoding, trigger_offset, size);
ui.set_completion(
editor,
items,
offset_encoding,
start_offset,
trigger_offset,
size,
);
};
},
);

View file

@ -171,6 +171,12 @@ impl Compositor {
(None, CursorKind::Hidden)
}
pub fn has_component(&self, type_name: &str) -> bool {
self.layers
.iter()
.any(|component| component.type_name() == type_name)
}
pub fn find(&mut self, type_name: &str) -> Option<&mut dyn Component> {
self.layers
.iter_mut()

View file

@ -69,14 +69,18 @@ impl menu::Item for CompletionItem {
/// Wraps a Menu.
pub struct Completion {
popup: Popup<Menu<CompletionItem>>,
start_offset: usize,
#[allow(dead_code)]
trigger_offset: usize,
// TODO: maintain a completioncontext with trigger kind & trigger char
}
impl Completion {
pub fn new(
editor: &Editor,
items: Vec<CompletionItem>,
offset_encoding: helix_lsp::OffsetEncoding,
start_offset: usize,
trigger_offset: usize,
) -> Self {
// let items: Vec<CompletionItem> = Vec::new();
@ -175,16 +179,22 @@ impl Completion {
};
});
let popup = Popup::new(menu);
Self {
let mut completion = Self {
popup,
start_offset,
trigger_offset,
}
};
// need to recompute immediately in case start_offset != trigger_offset
completion.recompute_filter(editor);
completion
}
pub fn update(&mut self, cx: &mut commands::Context) {
pub fn recompute_filter(&mut self, editor: &Editor) {
// recompute menu based on matches
let menu = self.popup.contents_mut();
let (view, doc) = current!(cx.editor);
let (view, doc) = current_ref!(editor);
// cx.hooks()
// cx.add_hook(enum type, ||)
@ -200,14 +210,22 @@ impl Completion {
.selection(view.id)
.primary()
.cursor(doc.text().slice(..));
if self.trigger_offset <= cursor {
let fragment = doc.text().slice(self.trigger_offset..cursor);
if self.start_offset <= cursor {
let fragment = doc.text().slice(self.start_offset..cursor);
let text = Cow::from(fragment);
// TODO: logic is same as ui/picker
menu.score(&text);
} else {
// we backspaced before the start offset, clear the menu
// this will cause the editor to remove the completion popup
menu.clear();
}
}
pub fn update(&mut self, cx: &mut commands::Context) {
self.recompute_filter(cx.editor)
}
pub fn is_empty(&self) -> bool {
self.popup.contents().is_empty()
}

View file

@ -37,7 +37,7 @@ pub struct EditorView {
keymaps: Keymaps,
on_next_key: Option<Box<dyn FnOnce(&mut commands::Context, KeyEvent)>>,
last_insert: (commands::Command, Vec<KeyEvent>),
completion: Option<Completion>,
pub(crate) completion: Option<Completion>,
spinners: ProgressSpinners,
autoinfo: Option<Info>,
}
@ -984,12 +984,21 @@ impl EditorView {
pub fn set_completion(
&mut self,
editor: &Editor,
items: Vec<helix_lsp::lsp::CompletionItem>,
offset_encoding: helix_lsp::OffsetEncoding,
start_offset: usize,
trigger_offset: usize,
size: Rect,
) {
let mut completion = Completion::new(items, offset_encoding, trigger_offset);
let mut completion =
Completion::new(editor, items, offset_encoding, start_offset, trigger_offset);
if completion.is_empty() {
// skip if we got no completion results
return;
}
// TODO : propagate required size on resize to completion too
completion.required_size((size.width, size.height));
self.completion = Some(completion);
@ -1211,6 +1220,7 @@ impl Component for EditorView {
EventResult::Consumed(None)
}
Event::Key(key) => {
cxt.editor.reset_idle_timer();
let mut key = KeyEvent::from(key);
canonicalize_key(&mut key);
// clear status
@ -1245,6 +1255,7 @@ impl Component for EditorView {
if callback.is_some() {
// assume close_fn
self.completion = None;
cxt.editor.clear_idle_timer(); // don't retrigger
}
}
}
@ -1258,6 +1269,7 @@ impl Component for EditorView {
completion.update(&mut cxt);
if completion.is_empty() {
self.completion = None;
cxt.editor.clear_idle_timer(); // don't retrigger
}
}
}

View file

@ -90,6 +90,14 @@ impl<T: Item> Menu<T> {
self.recalculate = true;
}
pub fn clear(&mut self) {
self.matches.clear();
// reset cursor position
self.cursor = None;
self.scroll = 0;
}
pub fn move_up(&mut self) {
let len = self.matches.len();
let pos = self.cursor.map_or(0, |i| (i + len.saturating_sub(1)) % len) % len;

View file

@ -271,12 +271,18 @@ impl<T> Picker<T> {
}
pub fn move_up(&mut self) {
if self.matches.is_empty() {
return;
}
let len = self.matches.len();
let pos = ((self.cursor + len.saturating_sub(1)) % len) % len;
self.cursor = pos;
}
pub fn move_down(&mut self) {
if self.matches.is_empty() {
return;
}
let len = self.matches.len();
let pos = (self.cursor + 1) % len;
self.cursor = pos;

View file

@ -13,10 +13,12 @@ use tokio_stream::wrappers::UnboundedReceiverStream;
use std::{
collections::HashMap,
path::{Path, PathBuf},
pin::Pin,
sync::Arc,
time::Duration,
};
use tokio::time::{sleep, Duration, Instant, Sleep};
use slotmap::SlotMap;
use anyhow::Error;
@ -29,6 +31,14 @@ use helix_dap as dap;
use serde::Deserialize;
fn deserialize_duration_millis<'de, D>(deserializer: D) -> Result<Duration, D::Error>
where
D: serde::Deserializer<'de>,
{
let millis = u64::deserialize(deserializer)?;
Ok(Duration::from_millis(millis))
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[serde(rename_all = "kebab-case", default)]
pub struct Config {
@ -42,12 +52,17 @@ pub struct Config {
pub shell: Vec<String>,
/// Line number mode.
pub line_number: LineNumber,
/// Middle click paste support. Defaults to true
/// Middle click paste support. Defaults to true.
pub middle_click_paste: bool,
/// Smart case: Case insensitive searching unless pattern contains upper case characters. Defaults to true.
pub smart_case: bool,
/// Automatic insertion of pairs to parentheses, brackets, etc. Defaults to true.
pub auto_pairs: bool,
/// Automatic auto-completion, automatically pop up without user trigger. Defaults to true.
pub auto_completion: bool,
/// Time in milliseconds since last keypress before idle timers trigger. Used for autocompletion, set to 0 for instant. Defaults to 400ms.
#[serde(skip_serializing, deserialize_with = "deserialize_duration_millis")]
pub idle_timeout: Duration,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
@ -75,6 +90,8 @@ impl Default for Config {
middle_click_paste: true,
smart_case: true,
auto_pairs: true,
auto_completion: true,
idle_timeout: Duration::from_millis(400),
}
}
}
@ -105,6 +122,8 @@ pub struct Editor {
pub status_msg: Option<(String, Severity)>,
pub config: Config,
pub idle_timer: Pin<Box<Sleep>>,
}
#[derive(Debug, Copy, Clone)]
@ -146,10 +165,24 @@ impl Editor {
registers: Registers::default(),
clipboard_provider: get_clipboard_provider(),
status_msg: None,
idle_timer: Box::pin(sleep(config.idle_timeout)),
config,
}
}
pub fn clear_idle_timer(&mut self) {
// equivalent to internal Instant::far_future() (30 years)
self.idle_timer
.as_mut()
.reset(Instant::now() + Duration::from_secs(86400 * 365 * 30));
}
pub fn reset_idle_timer(&mut self) {
self.idle_timer
.as_mut()
.reset(Instant::now() + self.config.idle_timeout);
}
pub fn clear_status(&mut self) {
self.status_msg = None;
}

View file

@ -44,3 +44,19 @@ macro_rules! view {
$( $editor ).+ .tree.get($( $editor ).+ .tree.focus)
}};
}
#[macro_export]
macro_rules! doc {
( $( $editor:ident ).+ ) => {{
$crate::current_ref!( $( $editor ).+ ).1
}};
}
#[macro_export]
macro_rules! current_ref {
( $( $editor:ident ).+ ) => {{
let view = $( $editor ).+ .tree.get($( $editor ).+ .tree.focus);
let doc = &$( $editor ).+ .documents[view.doc];
(view, doc)
}};
}

View file

@ -6,19 +6,11 @@ file-types = ["rs"]
roots = []
auto-format = true
comment-token = "//"
config = """
{
"cargo": {
"loadOutDirsFromCheck": true
},
"procMacro": {
"enable": false
}
}
"""
language-server = { command = "rust-analyzer" }
indent = { tab-width = 4, unit = " " }
[language.config]
cargo = { loadOutDirsFromCheck = true }
procMacro = { enable = false }
[language.debugger]
name = "lldb-vscode"
@ -133,6 +125,16 @@ request = "attach"
completion = [ "pid" ]
args = { console = "internalConsole", pid = "{0}" }
[[language]]
name = "c-sharp"
scope = "source.csharp"
injection-regex = "c-?sharp"
file-types = ["cs"]
roots = []
comment-token = "//"
indent = { tab-width = 4, unit = "\t" }
[[language]]
name = "go"
scope = "source.go"
@ -248,7 +250,7 @@ file-types = ["py"]
roots = []
comment-token = "#"
language-server = { command = "pyls" }
language-server = { command = "pylsp" }
# TODO: pyls needs utf-8 offsets
indent = { tab-width = 4, unit = " " }
@ -380,6 +382,15 @@ roots = []
indent = { tab-width = 2, unit = " " }
language-server = { command = "svelteserver", args = ["--stdio"] }
[[language]]
name = "vue"
scope = "source.vue"
injection-regex = "vue"
file-types = ["vue"]
roots = []
indent = { tab-width = 2, unit = " " }
[[language]]
name = "yaml"
scope = "source.yaml"
@ -409,3 +420,23 @@ comment-token = "//"
language-server = { command = "zls" }
indent = { tab-width = 4, unit = " " }
[[language]]
name = "prolog"
scope = "source.prolog"
roots = []
file-types = ["pl", "prolog"]
comment-token = "%"
language-server = { command = "swipl", args = [
"-g", "use_module(library(lsp_server))",
"-g", "lsp_server:main",
"-t", "halt", "--", "stdio"] }
[[language]]
name = "tsq"
scope = "source.tsq"
file-types = ["scm"]
roots = []
comment-token = ";"
indent = { tab-width = 2, unit = " " }

View file

@ -0,0 +1,238 @@
;; Methods
(method_declaration (identifier) @type (identifier) @function)
;; Types
(interface_declaration name: (identifier) @type)
(class_declaration name: (identifier) @type)
(enum_declaration name: (identifier) @type)
(struct_declaration (identifier) @type)
(record_declaration (identifier) @type)
(namespace_declaration name: (identifier) @type)
(constructor_declaration name: (identifier) @type)
[
(implicit_type)
(nullable_type)
(pointer_type)
(function_pointer_type)
(predefined_type)
] @type.builtin
;; Enum
(enum_member_declaration (identifier) @variable.property)
;; Literals
[
(real_literal)
(integer_literal)
] @number
[
(character_literal)
(string_literal)
(verbatim_string_literal)
(interpolated_string_text)
(interpolated_verbatim_string_text)
"\""
"$\""
"@$\""
"$@\""
] @string
[
(boolean_literal)
(null_literal)
(void_keyword)
] @constant.builtin
;; Comments
(comment) @comment
;; Tokens
[
";"
"."
","
] @punctuation.delimiter
[
"--"
"-"
"-="
"&"
"&&"
"+"
"++"
"+="
"<"
"<<"
"="
"=="
"!"
"!="
"=>"
">"
">>"
"|"
"||"
"?"
"??"
"^"
"~"
"*"
"/"
"%"
":"
] @operator
[
"("
")"
"["
"]"
"{"
"}"
] @punctuation.bracket
;; Keywords
(modifier) @keyword
(this_expression) @keyword
(escape_sequence) @keyword
[
"as"
"base"
"break"
"case"
"catch"
"checked"
"class"
"continue"
"default"
"delegate"
"do"
"else"
"enum"
"event"
"explicit"
"finally"
"for"
"foreach"
"goto"
"if"
"implicit"
"interface"
"is"
"lock"
"namespace"
"operator"
"params"
"return"
"sizeof"
"stackalloc"
"struct"
"switch"
"throw"
"try"
"typeof"
"unchecked"
"using"
"while"
"new"
"await"
"in"
"yield"
"get"
"set"
"when"
"out"
"ref"
"from"
"where"
"select"
"record"
"init"
"with"
"let"
] @keyword
;; Linq
(from_clause (identifier) @variable)
(group_clause)
(order_by_clause)
(select_clause (identifier) @variable)
(query_continuation (identifier) @variable) @keyword
;; Record
(with_expression
(with_initializer_expression
(simple_assignment_expression
(identifier) @variable)))
;; Exprs
(binary_expression (identifier) @variable (identifier) @variable)
(binary_expression (identifier)* @variable)
(conditional_expression (identifier) @variable)
(prefix_unary_expression (identifier) @variable)
(postfix_unary_expression (identifier)* @variable)
(assignment_expression (identifier) @variable)
(cast_expression (identifier) @type (identifier) @variable)
;; Class
(base_list (identifier) @type)
(property_declaration (generic_name))
(property_declaration
type: (nullable_type) @type
name: (identifier) @variable)
(property_declaration
type: (predefined_type) @type
name: (identifier) @variable)
(property_declaration
type: (identifier) @type
name: (identifier) @variable)
;; Lambda
(lambda_expression) @variable
;; Attribute
(attribute) @type
;; Parameter
(parameter
type: (identifier) @type
name: (identifier) @variable.parameter)
(parameter (identifier) @variable.parameter)
(parameter_modifier) @keyword
;; Typeof
(type_of_expression (identifier) @type)
;; Variable
(variable_declaration (identifier) @type)
(variable_declarator (identifier) @variable)
;; Return
(return_statement (identifier) @variable)
(yield_statement (identifier) @variable)
;; Type
(generic_name (identifier) @type)
(type_parameter (identifier) @variable.parameter)
(type_argument_list (identifier) @type)
;; Type constraints
(type_parameter_constraints_clause (identifier) @variable.parameter)
(type_constraint (identifier) @type)
;; Exception
(catch_declaration (identifier) @type (identifier) @variable)
(catch_declaration (identifier) @type)
;; Switch
(switch_statement (identifier) @variable)
(switch_expression (identifier) @variable)
;; Lock statement
(lock_statement (identifier) @variable)

View file

@ -47,7 +47,7 @@
; Variables
((identifier) @constant
(#match? @constant "^_*[A-Z][A-Z\d_]+"))
(#match? @constant "^_*[A-Z][A-Z\\d_]+$"))
(identifier) @variable

View file

@ -42,7 +42,7 @@
(relative_scope) @variable.builtin
((name) @constant
(#match? @constant "^_?[A-Z][A-Z\d_]+$"))
(#match? @constant "^_?[A-Z][A-Z\\d_]+$"))
((name) @constructor
(#match? @constructor "^[A-Z]"))

View file

@ -0,0 +1,46 @@
; mark the string passed #match? as a regex
(((predicate_name) @function
(capture)
(string) @string.regexp)
(#eq? @function "#match?"))
; highlight inheritance comments
((query . (comment) @keyword.directive)
(#match? @keyword.directive "^;\ +inherits *:"))
[
"("
")"
"["
"]"
] @punctuation.bracket
":" @punctuation.delimiter
[
(one_or_more)
(zero_or_one)
(zero_or_more)
] @operator
[
(wildcard_node)
(anchor)
] @constant.builtin
[
(anonymous_leaf)
(string)
] @string
(comment) @comment
(field_name) @property
(capture) @label
(predicate_name) @function
(escape_sequence) @escape
(node_name) @variable

View file

@ -0,0 +1,21 @@
(tag_name) @tag
(end_tag) @tag
(directive_name) @keyword
(directive_argument) @constant
(attribute
(attribute_name) @attribute
(quoted_attribute_value
(attribute_value) @string)
)
(comment) @comment
[
"<"
">"
"</"
"{{"
"}}"
] @punctuation.bracket

View file

@ -0,0 +1,17 @@
(directive_attribute
(directive_name) @keyword
(quoted_attribute_value
(attribute_value) @injection.content)
(#set! injection.language "javascript"))
((interpolation
(raw_text) @injection.content)
(#set! injection.language "javascript"))
((script_element
(raw_text) @injection.content)
(#set! injection.language "javascript"))
((style_element
(raw_text) @injection.content)
(#set! injection.language "css"))

View file

@ -1,7 +1,7 @@
# Author : RayGervais<raygervais@hotmail.ca>
# "ui.linenr.selected" = { fg = "#d8dee9" }
# "ui.text.focus" = { fg = "#e5ded6", modifiers= ["bold"] }
"ui.text.focus" = { fg = "#88c0d0", modifiers= ["bold"] }
# "ui.menu.selected" = { fg = "#e5ded6", bg = "#313f4e" }
# "info" = "#b48ead"
@ -24,8 +24,8 @@
"ui.cursor.match" = { bg = "434c5e" }
# nord3 - comments
"comment" = "#4c566a"
"ui.linenr" = { fg = "#4c566a" }
"comment" = "#616E88"
"ui.linenr" = { fg = "#616E88" }
# Snow Storm
# nord4 - cursor, variables, constants, attributes, fields

View file

@ -20,7 +20,6 @@ _________________________________________________________________
the first lesson.
=================================================================
= BASIC CURSOR MOVEMENT =
=================================================================
@ -43,7 +42,6 @@ _________________________________________________________________
=================================================================
= EXITING HELIX =
=================================================================
@ -66,7 +64,6 @@ _________________________________________________________________
=================================================================
= DELETION =
=================================================================
@ -89,7 +86,6 @@ _________________________________________________________________
=================================================================
= INSERT MODE =
=================================================================
@ -112,7 +108,6 @@ _________________________________________________________________
Note: The status bar will display your current mode.
Notice that when you press i, 'NOR' changes to 'INS'.
=================================================================
= MORE ON INSERT MODE =
=================================================================
@ -123,7 +118,7 @@ _________________________________________________________________
Common examples of insertion commands include:
i - Insert before the selection.
a - Insert after the selection. (a means "append")
a - Insert after the selection. (a means 'append')
I - Insert at the start of the line.
A - Insert at the end of the line.
@ -135,7 +130,6 @@ _________________________________________________________________
--> This sentence is miss
This sentence is missing some text.
=================================================================
= SAVING A FILE =
=================================================================
@ -158,7 +152,6 @@ _________________________________________________________________
=================================================================
= RECAP =
=================================================================
@ -181,7 +174,6 @@ _________________________________________________________________
=================================================================
= MOTIONS AND SELECTIONS =
=================================================================
@ -204,7 +196,6 @@ _________________________________________________________________
=================================================================
= MORE ON MOTIONS =
=================================================================
@ -227,6 +218,203 @@ _________________________________________________________________
=================================================================
= THE CHANGE COMMAND =
=================================================================
Press c to change the current selection.
The change command deletes the current selection and enters
Insert mode, so it is a very common shorthand for di.
1. Move the cursor to the line below marked -->.
2. Move to the start of an incorrect word and press w to
select it.
3. Press c to delete the word and enter Insert mode.
4. Type the correct word.
5. Repeat until the line matches the line below it.
--> This paper has heavy words behind it.
This sentence has incorrect words in it.
=================================================================
= COUNTS WITH MOTIONS =
=================================================================
Type a number before a motion to repeat it that many times.
1. Move the cursor to the line below marked -->.
2. Type 2w to move 2 words forward.
3. Type 3e to move to the end of the third word forward.
4. Type 2b to move 2 words backwards
5. Try the above with different numbers.
--> This is just a line with words you can move around in.
=================================================================
= SELECTING LINES =
=================================================================
Press x to select a whole line. Press again to select the next.
1. Move the cursor to the second line below marked -->.
2. Press x to select the line, and d to delete it.
3. Move to the fourth line.
4. Press x twice or type 2x to select 2 lines, and d to delete.
--> 1) Roses are red,
--> 2) Mud is fun,
--> 3) Violets are blue,
--> 4) I have a car,
--> 5) Clocks tell time,
--> 6) Sugar is sweet,
--> 7) And so are you.
=================================================================
= UNDOING =
=================================================================
Type u to undo. Type U to redo.
1. Move the cursor to the line below marked -->.
2. Move to the first error, and press d to delete it.
3. Type u to undo your deletion.
4. Fix all the errors on the line.
5. Type u several times to undo your fixes.
6. Type U (<SHIFT> + u) several times to redo your fixes.
--> Fiix the errors on thhis line and reeplace them witth undo.
=================================================================
= RECAP =
=================================================================
* Type w to select forward until the next word.
* Type e to select to the end of the current word.
* Type b to select backward to the start of the current word.
* Use uppercase counterparts, W,E,B, to traverse WORDS.
* Typing d deletes the entire selection, so you can delete a
word forward by typing wd.
* Type c to delete the selection and enter Insert mode.
* Type a number before a motion to repeat it that many times.
* Type x to select the entire current line. Type x again to
select the next line.
* Type u to undo. Type U to redo.
=================================================================
= MULTIPLE CURSORS =
=================================================================
Type C to duplicate the cursor to the next line.
1. Move the cursor to the first line below marked -->.
2. Type C to duplicate the cursor to the next line. Keys you
press will now affect both cursors.
3. Use Insert mode to correct the lines. The two cursors will
fix both lines simultaneously.
4. Type , to remove the second cursor.
--> Fix th two nes at same ime.
--> Fix th two nes at same ime.
Fix these two lines at the same time.
=================================================================
= THE SELECT COMMAND =
=================================================================
Type s to select matches in the selection.
1. Move the cursor to the line below marked -->.
2. Press x to select the line.
3. Press s. A prompt will appear.
4. Type 'apples' and press <ENTER>. Both occurrences of
'apples' in the line will be selected.
5. You can now press c and change 'apples' to something else,
like 'oranges'.
6. Type , to remove the second cursor.
--> I like to eat apples since my favorite fruit is apples.
=================================================================
= SELECTING VIA REGEX =
=================================================================
The select command selects regular expressions, not just exact
matches, allowing you to target more complex patterns.
1. Move the cursor to the line below marked -->.
2. Select the line with x and then press s.
3. Enter ' +' to select any amount of consecutive spaces >1.
4. Press c and change the matches to single spaces.
--> This sentence has some extra spaces.
Note: If you want to perform find-and-replace, the select
command is the way to do it. Select the text you want
to replace in — type % to select the whole file — and
then perform the steps explained above.
=================================================================
= COLLAPSING SELECTIONS =
=================================================================
Type ; to collapse selections to single cursors.
Sometimes, you want to deselect without having to move the
cursor(s). This can be done using the ; key.
1. Move the cursor to the line below marked -->.
2. Use the motions you have learned to move around the line,
and try using ; to deselect the text after it is selected
by the motions.
--> This is an error-free line with words to move around in.
=================================================================
This tutorial is still a work-in-progress.