aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBlaž Hrastnik2021-10-17 04:51:56 +0000
committerBlaž Hrastnik2021-10-17 04:51:56 +0000
commit0a6b60085a56876068bf652c9491c435e9cca229 (patch)
tree8681db55d1a7707421f98d81a108d6438459536a
parentbc0084d071ac53fa460370667cdd8740e7499c76 (diff)
parente216e9621e73cda1968632cd20595231af5e07be (diff)
Merge branch 'master' into debug
-rw-r--r--.gitmodules8
-rw-r--r--Cargo.lock69
-rw-r--r--README.md2
-rw-r--r--book/src/configuration.md2
-rw-r--r--book/theme/css/chrome.css2
-rw-r--r--book/theme/css/general.css5
-rw-r--r--book/theme/css/variables.css14
-rw-r--r--helix-core/Cargo.toml1
-rw-r--r--helix-core/src/selection.rs8
-rw-r--r--helix-core/src/syntax.rs13
-rw-r--r--helix-lsp/src/lib.rs10
m---------helix-syntax/languages/tree-sitter-tsq0
m---------helix-syntax/languages/tree-sitter-vue0
-rw-r--r--helix-term/src/application.rs71
-rw-r--r--helix-term/src/commands.rs42
-rw-r--r--helix-term/src/compositor.rs6
-rw-r--r--helix-term/src/ui/completion.rs30
-rw-r--r--helix-term/src/ui/editor.rs16
-rw-r--r--helix-term/src/ui/menu.rs8
-rw-r--r--helix-term/src/ui/picker.rs6
-rw-r--r--helix-view/src/editor.rs37
-rw-r--r--helix-view/src/macros.rs16
-rw-r--r--languages.toml55
-rw-r--r--runtime/queries/c-sharp/highlights.scm238
-rw-r--r--runtime/queries/java/highlights.scm2
-rw-r--r--runtime/queries/php/highlights.scm2
-rw-r--r--runtime/queries/tsq/highlights.scm46
-rw-r--r--runtime/queries/vue/highlights.scm21
-rw-r--r--runtime/queries/vue/injections.scm17
-rw-r--r--runtime/themes/nord.toml6
-rw-r--r--runtime/tutor.txt208
31 files changed, 844 insertions, 117 deletions
diff --git a/.gitmodules b/.gitmodules
index d1fc1517..a8e6481e 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -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
diff --git a/Cargo.lock b/Cargo.lock
index 436f9071..9631ba8e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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",
diff --git a/README.md b/README.md
index d596c83d..7cd58d59 100644
--- a/README.md
+++ b/README.md
@@ -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).
diff --git a/book/src/configuration.md b/book/src/configuration.md
index 60b12bfd..d47f95d9 100644
--- a/book/src/configuration.md
+++ b/book/src/configuration.md
@@ -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
diff --git a/book/theme/css/chrome.css b/book/theme/css/chrome.css
index 72b7f188..aba8a417 100644
--- a/book/theme/css/chrome.css
+++ b/book/theme/css/chrome.css
@@ -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;
}
diff --git a/book/theme/css/general.css b/book/theme/css/general.css
index ddc2387a..9b280d08 100644
--- a/book/theme/css/general.css
+++ b/book/theme/css/general.css
@@ -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;
+}
diff --git a/book/theme/css/variables.css b/book/theme/css/variables.css
index db1a11b8..b62c7558 100644
--- a/book/theme/css/variables.css
+++ b/book/theme/css/variables.css
@@ -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;
diff --git a/helix-core/Cargo.toml b/helix-core/Cargo.toml
index 27adceb2..20ba47e9 100644
--- a/helix-core/Cargo.toml
+++ b/helix-core/Cargo.toml
@@ -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"
diff --git a/helix-core/src/selection.rs b/helix-core/src/selection.rs
index 755ee679..18af4d08 100644
--- a/helix-core/src/selection.rs
+++ b/helix-core/src/selection.rs
@@ -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.
diff --git a/helix-core/src/syntax.rs b/helix-core/src/syntax.rs
index 996823ce..00c09ea2 100644
--- a/helix-core/src/syntax.rs
+++ b/helix-core/src/syntax.rs
@@ -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,
diff --git a/helix-lsp/src/lib.rs b/helix-lsp/src/lib.rs
index 35cff754..7fa65928 100644
--- a/helix-lsp/src/lib.rs
+++ b/helix-lsp/src/lib.rs
@@ -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));
diff --git a/helix-syntax/languages/tree-sitter-tsq b/helix-syntax/languages/tree-sitter-tsq
new file mode 160000
+Subproject b665659d3238e6036e22ed0e24935e60efb3941
diff --git a/helix-syntax/languages/tree-sitter-vue b/helix-syntax/languages/tree-sitter-vue
new file mode 160000
+Subproject 91fe2754796cd8fba5f229505a23fa08f3546c0
diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs
index b99fccdf..d8ff2a8a 100644
--- a/helix-term/src/application.rs
+++ b/helix-term/src/application.rs
@@ -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();
diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs
index 6a678de1..f3761d7d 100644
--- a/helix-term/src/commands.rs
+++ b/helix-term/src/commands.rs
@@ -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,
+ );
};
},
);
diff --git a/helix-term/src/compositor.rs b/helix-term/src/compositor.rs
index 36e54ede..cad1df05 100644
--- a/helix-term/src/compositor.rs
+++ b/helix-term/src/compositor.rs
@@ -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()
diff --git a/helix-term/src/ui/completion.rs b/helix-term/src/ui/completion.rs
index 6c9e3a80..c75b24f1 100644
--- a/helix-term/src/ui/completion.rs
+++ b/helix-term/src/ui/completion.rs
@@ -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()
}
diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs
index 128fe948..037f04b8 100644
--- a/helix-term/src/ui/editor.rs
+++ b/helix-term/src/ui/editor.rs
@@ -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
}
}
}
diff --git a/helix-term/src/ui/menu.rs b/helix-term/src/ui/menu.rs
index dab0c34f..055593fd 100644
--- a/helix-term/src/ui/menu.rs
+++ b/helix-term/src/ui/menu.rs
@@ -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;
diff --git a/helix-term/src/ui/picker.rs b/helix-term/src/ui/picker.rs
index ee1ec177..341235ee 100644
--- a/helix-term/src/ui/picker.rs
+++ b/helix-term/src/ui/picker.rs
@@ -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;
diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs
index 72140ea8..60864e9e 100644
--- a/helix-view/src/editor.rs
+++ b/helix-view/src/editor.rs
@@ -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;
}
diff --git a/helix-view/src/macros.rs b/helix-view/src/macros.rs
index c9a04270..0bebd02f 100644
--- a/helix-view/src/macros.rs
+++ b/helix-view/src/macros.rs
@@ -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)
+ }};
+}
diff --git a/languages.toml b/languages.toml
index a5640c21..981da557 100644
--- a/languages.toml
+++ b/languages.toml
@@ -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"
@@ -134,6 +126,16 @@ 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"
injection-regex = "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 = " " }
diff --git a/runtime/queries/c-sharp/highlights.scm b/runtime/queries/c-sharp/highlights.scm
new file mode 100644
index 00000000..b76f4e60
--- /dev/null
+++ b/runtime/queries/c-sharp/highlights.scm
@@ -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)
diff --git a/runtime/queries/java/highlights.scm b/runtime/queries/java/highlights.scm
index 3f8ae0d5..e7d793df 100644
--- a/runtime/queries/java/highlights.scm
+++ b/runtime/queries/java/highlights.scm
@@ -47,7 +47,7 @@
; Variables
((identifier) @constant
- (#match? @constant "^_*[A-Z][A-Z\d_]+"))
+ (#match? @constant "^_*[A-Z][A-Z\\d_]+$"))
(identifier) @variable
diff --git a/runtime/queries/php/highlights.scm b/runtime/queries/php/highlights.scm
index 83850403..02904555 100644
--- a/runtime/queries/php/highlights.scm
+++ b/runtime/queries/php/highlights.scm
@@ -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]"))
diff --git a/runtime/queries/tsq/highlights.scm b/runtime/queries/tsq/highlights.scm
new file mode 100644
index 00000000..9ba5699a
--- /dev/null
+++ b/runtime/queries/tsq/highlights.scm
@@ -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
diff --git a/runtime/queries/vue/highlights.scm b/runtime/queries/vue/highlights.scm
new file mode 100644
index 00000000..f90ae429
--- /dev/null
+++ b/runtime/queries/vue/highlights.scm
@@ -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 \ No newline at end of file
diff --git a/runtime/queries/vue/injections.scm b/runtime/queries/vue/injections.scm
new file mode 100644
index 00000000..8ee34ffb
--- /dev/null
+++ b/runtime/queries/vue/injections.scm
@@ -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"))
diff --git a/runtime/themes/nord.toml b/runtime/themes/nord.toml
index c6a0e172..ee7c8865 100644
--- a/runtime/themes/nord.toml
+++ b/runtime/themes/nord.toml
@@ -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
diff --git a/runtime/tutor.txt b/runtime/tutor.txt
index 07b88884..b6f600d0 100644
--- a/runtime/tutor.txt
+++ b/runtime/tutor.txt
@@ -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.