diff --git a/.github/workflows/web_demo.yml b/.github/workflows/web_demo.yml new file mode 100644 index 000000000000..2036b0aabb3d --- /dev/null +++ b/.github/workflows/web_demo.yml @@ -0,0 +1,50 @@ +name: Deploy web demo +on: + push: + branches: [ wasm32 ] +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout sources + uses: actions/checkout@v4 + + - name: Setup node + uses: actions/setup-node@v3 + with: + node-version: 20 + + - name: Install LLVM and Clang + uses: KyleMayes/install-llvm-action@v1 + with: + version: "16" + + - name: Set up cache + uses: Swatinem/rust-cache@v2 + + - name: Install wasm-pack + uses: jetli/wasm-pack-action@v0.4.0 + with: + version: 'latest' + + - name: Build Helix webpack + run: | + cd helix-web + wasm-pack build + wasm-pack pack + + - name: Install node modules and build static app + run: | + cd helix-web/www + npm install ../pkg/helix-web-0.1.0.tgz + NODE_OPTIONS=--openssl-legacy-provider npm run build + cp node_modules/xterm/css/xterm.css dist/ + + - name: Deploy + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./helix-web/www/dist/ + destination_dir: ./demo + force_orphan: true diff --git a/Cargo.lock b/Cargo.lock index 3a88401ed3fa..12cd9a00acc1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -152,6 +152,26 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +[[package]] +name = "c2rust-bitfields" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb34f0c0ace43530b2df7f18bc69ee0c4082158aa451ece29602f8c841e73764" +dependencies = [ + "c2rust-bitfields-derive", +] + +[[package]] +name = "c2rust-bitfields-derive" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dd1601a7b828ab874d890e5a895563ca8ad485bdd3d2a359f148c8b72537241" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "cassowary" version = "0.3.0" @@ -223,6 +243,26 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "console_log" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be8aed40e4edbf4d3b4431ab260b63fdc40f5780a4766824329ea0f1eefe3c0f" +dependencies = [ + "log", + "web-sys", +] + [[package]] name = "content_inspector" version = "0.2.4" @@ -299,18 +339,18 @@ dependencies = [ [[package]] name = "crossterm" version = "0.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" +source = "git+https://github.com/makemeunsee/crossterm?branch=wasm32#7f843da1a8c1f58d7c067fd6ee070c1a44f8e225" dependencies = [ "bitflags 2.4.1", "crossterm_winapi", - "filedescriptor", "futures-core", "libc", "mio", "parking_lot", + "rs-xterm-js", "signal-hook", "signal-hook-mio", + "wasm-bindgen", "winapi", ] @@ -490,17 +530,6 @@ dependencies = [ "log", ] -[[package]] -name = "filedescriptor" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7199d965852c3bac31f779ef99cbb4537f80e952e2d6aa0ffeb30cce00f4f46e" -dependencies = [ - "libc", - "thiserror", - "winapi", -] - [[package]] name = "flate2" version = "1.0.27" @@ -526,6 +555,15 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futures-channel" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +dependencies = [ + "futures-core", +] + [[package]] name = "futures-core" version = "0.3.28" @@ -569,8 +607,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -1052,6 +1092,18 @@ dependencies = [ "regex", ] +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "grep-matcher" version = "0.1.6" @@ -1118,10 +1170,12 @@ dependencies = [ "dunce", "encoding_rs", "etcetera", + "getrandom", "hashbrown 0.14.2", "helix-loader", "imara-diff", "indoc", + "instant", "log", "nucleo", "once_cell", @@ -1135,11 +1189,13 @@ dependencies = [ "smallvec", "smartstring", "textwrap", + "thiserror", "toml", - "tree-sitter", + "tree-sitter-c2rust", "unicode-general-category", "unicode-segmentation", "unicode-width", + "web-sys", ] [[package]] @@ -1180,7 +1236,7 @@ dependencies = [ "tempfile", "threadpool", "toml", - "tree-sitter", + "tree-sitter-c2rust", "which", ] @@ -1220,6 +1276,7 @@ dependencies = [ "content_inspector", "crossterm", "fern", + "futures-executor", "futures-util", "grep-regex", "grep-searcher", @@ -1233,6 +1290,7 @@ dependencies = [ "helix-view", "ignore", "indoc", + "instant", "libc", "log", "nucleo", @@ -1259,10 +1317,12 @@ dependencies = [ "crossterm", "helix-core", "helix-view", + "instant", "log", "once_cell", "serde", "termini", + "tokio", "unicode-segmentation", ] @@ -1292,14 +1352,16 @@ dependencies = [ "chardetng", "clipboard-win", "crossterm", + "futures-executor", "futures-util", + "gloo-timers", "helix-core", "helix-dap", "helix-event", "helix-loader", "helix-lsp", - "helix-tui", "helix-vcs", + "instant", "libc", "log", "once_cell", @@ -1312,9 +1374,35 @@ dependencies = [ "tokio-stream", "toml", "url", + "wasm-bindgen", "which", ] +[[package]] +name = "helix-web" +version = "0.1.0" +dependencies = [ + "cc", + "console_error_panic_hook", + "console_log", + "crossterm", + "futures-util", + "gloo-timers", + "helix-core", + "helix-loader", + "helix-term", + "helix-tui", + "helix-view", + "js-sys", + "log", + "rs-xterm-js", + "tokio", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test", + "web-sys", +] + [[package]] name = "hermit-abi" version = "0.2.6" @@ -1410,6 +1498,18 @@ version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8" +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "itoa" version = "1.0.6" @@ -1418,9 +1518,9 @@ checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" [[package]] name = "js-sys" -version = "0.3.61" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" dependencies = [ "wasm-bindgen", ] @@ -1821,6 +1921,16 @@ dependencies = [ "str_indices", ] +[[package]] +name = "rs-xterm-js" +version = "0.1.0" +source = "git+https://github.com/makemeunsee/rs-xterm-js#a88cf395e52cea3e2fe66c9eb19240b319f54f91" +dependencies = [ + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -1855,6 +1965,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "scopeguard" version = "1.1.0" @@ -2258,11 +2374,13 @@ dependencies = [ ] [[package]] -name = "tree-sitter" +name = "tree-sitter-c2rust" version = "0.20.10" -source = "git+https://github.com/tree-sitter/tree-sitter?rev=ab09ae20d640711174b8da8a654f6b3dec93da1a#ab09ae20d640711174b8da8a654f6b3dec93da1a" +source = "git+https://github.com/makemeunsee/tree-sitter-c2rust#f3e377823e8b7cdda872205f63235b85bcb09969" dependencies = [ - "cc", + "c2rust-bitfields", + "instant", + "once_cell", "regex", ] @@ -2362,9 +2480,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.84" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -2372,24 +2490,36 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.84" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.38", "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" -version = "0.2.84" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2397,22 +2527,56 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.84" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.38", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.84" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" + +[[package]] +name = "wasm-bindgen-test" +version = "0.3.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e6e302a7ea94f83a6d09e78e7dc7d9ca7b186bc2829c24a22d0753efd680671" +dependencies = [ + "console_error_panic_hook", + "js-sys", + "scoped-tls", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test-macro", +] + +[[package]] +name = "wasm-bindgen-test-macro" +version = "0.3.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecb993dd8c836930ed130e020e77d9b2e65dd0fbab1b67c790b0f5d80b11a575" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "web-sys" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" +dependencies = [ + "js-sys", + "wasm-bindgen", +] [[package]] name = "which" diff --git a/Cargo.toml b/Cargo.toml index 8ffe0fa7ac84..6e70088140ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,12 +10,12 @@ members = [ "helix-loader", "helix-vcs", "helix-parsec", + "helix-web", "xtask", ] +resolver = "2" -default-members = [ - "helix-term" -] +default-members = ["helix-term"] [profile.release] lto = "thin" @@ -35,5 +35,6 @@ package.helix-tui.opt-level = 2 package.helix-term.opt-level = 2 [workspace.dependencies] -tree-sitter = { version = "0.20", git = "https://github.com/tree-sitter/tree-sitter", rev = "ab09ae20d640711174b8da8a654f6b3dec93da1a" } +tree-sitter = { package = "tree-sitter-c2rust", git = "https://github.com/makemeunsee/tree-sitter-c2rust" } +crossterm = { git = "https://github.com/makemeunsee/crossterm", branch = "wasm32", features = ["event-stream"] } nucleo = "0.2.0" diff --git a/helix-core/Cargo.toml b/helix-core/Cargo.toml index f0903f42bc88..47f5354f0d0d 100644 --- a/helix-core/Cargo.toml +++ b/helix-core/Cargo.toml @@ -24,13 +24,14 @@ unicode-segmentation = "1.10" unicode-width = "0.1" unicode-general-category = "0.6" # slab = "0.4.2" +once_cell = "1.18" slotmap = "1.0" tree-sitter.workspace = true -once_cell = "1.18" arc-swap = "1" regex = "1" bitflags = "2.4" ahash = "0.8.5" +getrandom = { version = "*", features = ["js"] } hashbrown = { version = "0.14.2", features = ["raw"] } dunce = "1.0" @@ -43,14 +44,25 @@ imara-diff = "0.1.0" encoding_rs = "0.8" -chrono = { version = "0.4", default-features = false, features = ["alloc", "std"] } +chrono = { version = "0.4", default-features = false, features = [ + "alloc", + "std", +] } -etcetera = "0.8" textwrap = "0.16.0" nucleo.workspace = true parking_lot = "0.12" +thiserror = "1.0" + +[target.'cfg(target_arch = "wasm32")'.dependencies] +instant = { version = "0.1", features = ["wasm-bindgen"] } +web-sys = { version = "0.3", features = ["Storage", "Window"] } + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +etcetera = "0.8" + [dev-dependencies] quickcheck = { version = "1", default-features = false } indoc = "2.0.4" diff --git a/helix-core/src/diff.rs b/helix-core/src/diff.rs index a5d6d72298be..743154a41ddb 100644 --- a/helix-core/src/diff.rs +++ b/helix-core/src/diff.rs @@ -1,4 +1,8 @@ use std::ops::Range; + +#[cfg(target_arch = "wasm32")] +use instant::Instant; +#[cfg(not(target_arch = "wasm32"))] use std::time::Instant; use imara_diff::intern::InternedInput; diff --git a/helix-core/src/history.rs b/helix-core/src/history.rs index 28d6dd6ec1a0..0546656a69b2 100644 --- a/helix-core/src/history.rs +++ b/helix-core/src/history.rs @@ -2,7 +2,12 @@ use crate::{Assoc, ChangeSet, Range, Rope, Selection, Transaction}; use once_cell::sync::Lazy; use regex::Regex; use std::num::NonZeroUsize; -use std::time::{Duration, Instant}; +use std::time::Duration; + +#[cfg(target_arch = "wasm32")] +use instant::Instant; +#[cfg(not(target_arch = "wasm32"))] +use std::time::Instant; #[derive(Debug, Clone)] pub struct State { diff --git a/helix-core/src/lib.rs b/helix-core/src/lib.rs index 0acdb238054c..f0627cde9b2b 100644 --- a/helix-core/src/lib.rs +++ b/helix-core/src/lib.rs @@ -22,6 +22,8 @@ mod position; pub mod search; pub mod selection; pub mod shellwords; +#[cfg(target_arch = "wasm32")] +pub mod storage; pub mod surround; pub mod syntax; pub mod test; diff --git a/helix-core/src/path.rs b/helix-core/src/path.rs index ede37e044e05..fd2a60b67ff6 100644 --- a/helix-core/src/path.rs +++ b/helix-core/src/path.rs @@ -1,9 +1,11 @@ +#[cfg(not(target_arch = "wasm32"))] use etcetera::home_dir; use std::path::{Component, Path, PathBuf}; /// Replaces users home directory from `path` with tilde `~` if the directory /// is available, otherwise returns the path unchanged. -pub fn fold_home_dir(path: &Path) -> PathBuf { +fn fold_home_dir(path: &Path) -> PathBuf { + #[cfg(not(target_arch = "wasm32"))] if let Ok(home) = home_dir() { if let Ok(stripped) = path.strip_prefix(&home) { return PathBuf::from("~").join(stripped); @@ -20,6 +22,7 @@ pub fn expand_tilde(path: &Path) -> PathBuf { let mut components = path.components().peekable(); if let Some(Component::Normal(c)) = components.peek() { if c == &"~" { + #[cfg(not(target_arch = "wasm32"))] if let Ok(home) = home_dir() { // it's ok to unwrap, the path starts with `~` return home.join(path.strip_prefix("~").unwrap()); diff --git a/helix-core/src/storage.rs b/helix-core/src/storage.rs new file mode 100644 index 000000000000..3645b8a87b4d --- /dev/null +++ b/helix-core/src/storage.rs @@ -0,0 +1,142 @@ +use std::{ + io::{ErrorKind, Read, Result, Write}, + path::{Path, PathBuf}, +}; + +use log::debug; +use web_sys::Storage; + +pub struct WebStorage { + id: String, + content: Vec, + read_pos: usize, + was_written: bool, +} + +impl Read for WebStorage { + fn read(&mut self, mut buf: &mut [u8]) -> Result { + debug!( + "storage, read request for max. {} bytes; already read: {}, total {}", + buf.len(), + self.read_pos, + self.content.len() + ); + let remaining = self.content.len() - self.read_pos; + let capacity = buf.len(); + if capacity >= remaining { + let from = self.read_pos; + self.read_pos += remaining; + buf.write(&self.content[from..]) + } else { + let from = self.read_pos; + self.read_pos += capacity; + buf.write(&self.content[from..self.read_pos]) + } + } +} + +impl Write for WebStorage { + fn write(&mut self, buf: &[u8]) -> Result { + debug!("storage, write request for {} bytes", buf.len()); + if self.was_written { + self.content.extend_from_slice(buf); + } else { + self.content = buf.to_vec(); + } + debug!("storage, storing {} bytes", self.content.len()); + let storage = storage()?; + if let Ok(content) = String::from_utf8(self.content.to_vec()) { + match storage.set_item(&self.id, &content) { + Ok(_) => { + self.was_written = true; + Ok(buf.len()) + } + Err(e) => { + debug!("error writing to storage {:?}", e); + Err(ErrorKind::Other.into()) + } + } + } else { + debug!("storage only supports writing UTF-8 content"); + Err(ErrorKind::InvalidData.into()) + } + } + + fn flush(&mut self) -> Result<()> { + Ok(()) + } +} + +pub fn exists>(path: P) -> bool { + fn inner(path: &Path) -> bool { + if let Ok(storage) = storage() { + let path = path.to_string_lossy(); + match storage.get_item(&path) { + Ok(Some(_)) => true, + _ => false, + } + } else { + false + } + } + inner(path.as_ref()) +} + +pub fn open>(path: P) -> Result { + fn inner(path: &Path) -> Result { + let storage = storage()?; + let path = path.to_string_lossy(); + match storage.get_item(&path) { + Ok(Some(content)) => Ok(WebStorage { + id: path.into_owned(), + content: content.into_bytes().to_vec(), + read_pos: 0, + was_written: false, + }), + Ok(None) => { + debug!("content not found in storage"); + Ok(WebStorage { + id: path.into_owned(), + content: vec![], + read_pos: 0, + was_written: false, + }) + } + Err(e) => { + debug!("error accessing storage {:?}", e); + Err(ErrorKind::Other.into()) + } + } + } + inner(path.as_ref()) +} + +pub fn read_to_string(path: PathBuf) -> Result { + let path = path.to_string_lossy(); + let storage = storage()?; + match storage.get_item(&path) { + Ok(Some(content)) => Ok(content), + Ok(None) => { + debug!("nothing found in storage for path {}", &path); + Err(ErrorKind::NotFound.into()) + } + Err(e) => { + debug!("error reading content from storage: {:?}", e); + Err(ErrorKind::Other.into()) + } + } +} + +fn storage() -> Result { + match web_sys::window().unwrap().local_storage() { + Ok(Some(storage)) => Ok(storage), + Ok(None) => { + debug!("no storage available"); + Err(ErrorKind::Unsupported.into()) + } + Err(e) => { + debug!("error accessing storage: {:?}", e); + Err(ErrorKind::Other.into()) + } + } +} diff --git a/helix-core/src/syntax.rs b/helix-core/src/syntax.rs index 881b45098a9e..718b09f227e4 100644 --- a/helix-core/src/syntax.rs +++ b/helix-core/src/syntax.rs @@ -28,7 +28,10 @@ use std::{ use once_cell::sync::{Lazy, OnceCell}; use serde::{ser::SerializeSeq, Deserialize, Serialize}; -use helix_loader::grammar::{get_language, load_runtime_file}; +#[cfg(not(target_arch = "wasm32"))] +use helix_loader::grammar::load_runtime_file; + +use helix_loader::grammar::get_language; fn deserialize_regex<'de, D>(deserializer: D) -> Result, D::Error> where @@ -601,6 +604,7 @@ impl TextObjectQuery { } } +#[cfg(not(target_arch = "wasm32"))] pub fn read_query(language: &str, filename: &str) -> String { static INHERITS_REGEX: Lazy = Lazy::new(|| Regex::new(r";+\s*inherits\s*:?\s*([a-z_,()-]+)\s*").unwrap()); @@ -617,6 +621,52 @@ pub fn read_query(language: &str, filename: &str) -> String { }) .to_string() } +#[cfg(target_arch = "wasm32")] +pub fn read_query(language: &str, filename: &str) -> String { + const C_HIGHLIGHTS: &str = include_str!("../../runtime/queries/c/highlights.scm"); + const C_INJECTIONS: &str = include_str!("../../runtime/queries/c/injections.scm"); + + const JAVA_HIGHLIGHTS: &str = include_str!("../../runtime/queries/java/highlights.scm"); + const JAVA_INJECTIONS: &str = include_str!("../../runtime/queries/java/injections.scm"); + + const REGEX_HIGHLIGHTS: &str = include_str!("../../runtime/queries/regex/highlights.scm"); + + const RUST_HIGHLIGHTS: &str = include_str!("../../runtime/queries/rust/highlights.scm"); + const RUST_INJECTIONS: &str = include_str!("../../runtime/queries/rust/injections.scm"); + const RUST_LOCALS: &str = include_str!("../../runtime/queries/rust/locals.scm"); + + const TOML_HIGHLIGHTS: &str = include_str!("../../runtime/queries/toml/highlights.scm"); + const TOML_INJECTIONS: &str = include_str!("../../runtime/queries/toml/injections.scm"); + + match language { + "c" => match filename { + "highlights.scm" => C_HIGHLIGHTS.to_string(), + "injections.scm" => C_INJECTIONS.to_string(), + _ => String::default(), + }, + "java" => match filename { + "highlights.scm" => JAVA_HIGHLIGHTS.to_string(), + "injections.scm" => JAVA_INJECTIONS.to_string(), + _ => String::default(), + }, + "regex" => match filename { + "highlights.scm" => REGEX_HIGHLIGHTS.to_string(), + _ => String::default(), + }, + "rust" => match filename { + "highlights.scm" => RUST_HIGHLIGHTS.to_string(), + "injections.scm" => RUST_INJECTIONS.to_string(), + "locals.scm" => RUST_LOCALS.to_string(), + _ => String::default(), + }, + "toml" => match filename { + "highlights.scm" => TOML_HIGHLIGHTS.to_string(), + "injections.scm" => TOML_INJECTIONS.to_string(), + _ => String::default(), + }, + _ => String::default(), + } +} impl LanguageConfiguration { fn initialize_highlight(&self, scopes: &[String]) -> Option> { @@ -628,6 +678,10 @@ impl LanguageConfiguration { let locals_query = read_query(&self.language_id, "locals.scm"); if highlights_query.is_empty() { + log::error!( + "No tree sitter queries available for language {}", + self.language_id + ); None } else { let language = get_language(self.grammar.as_deref().unwrap_or(&self.language_id)) @@ -1614,6 +1668,7 @@ impl<'a> Iterator for ChunksBytes<'a> { } pub struct RopeProvider<'a>(pub RopeSlice<'a>); + impl<'a> TextProvider<&'a [u8]> for RopeProvider<'a> { type I = ChunksBytes<'a>; diff --git a/helix-event/Cargo.toml b/helix-event/Cargo.toml index 5cd955588960..40a080c44c83 100644 --- a/helix-event/Cargo.toml +++ b/helix-event/Cargo.toml @@ -11,5 +11,15 @@ homepage = "https://helix-editor.com" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -tokio = { version = "1", features = ["rt", "rt-multi-thread", "time", "sync", "parking_lot"] } parking_lot = { version = "0.12", features = ["send_guard"] } + +tokio = { version = "1", features = ["sync"] } + +[target.'cfg(not(target_family = "wasm"))'.dependencies] +tokio = { version = "1", features = [ + "rt", + "rt-multi-thread", + "time", + "sync", + "parking_lot", +] } diff --git a/helix-loader/Cargo.toml b/helix-loader/Cargo.toml index 8e52e9725f77..576279e71dea 100644 --- a/helix-loader/Cargo.toml +++ b/helix-loader/Cargo.toml @@ -13,23 +13,24 @@ homepage = "https://helix-editor.com" name = "hx-loader" path = "src/main.rs" +[features] +default = [] + [dependencies] anyhow = "1" serde = { version = "1.0", features = ["derive"] } toml = "0.7" -etcetera = "0.8" tree-sitter.workspace = true once_cell = "1.18" log = "0.4" -which = "4.4" - -# TODO: these two should be on !wasm32 only -# cloning/compiling tree-sitter grammars -cc = { version = "1" } -threadpool = { version = "1.0" } -tempfile = "3.8.0" dunce = "1.0.4" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] +threadpool = { version = "1.0" } +tempfile = "3.8.0" libloading = "0.8" +which = "4.4" +etcetera = "0.8" +# cloning/compiling tree-sitter grammars +cc = { version = "1" } diff --git a/helix-loader/build.rs b/helix-loader/build.rs index 74ba2a2beb2a..97316103c6fd 100644 --- a/helix-loader/build.rs +++ b/helix-loader/build.rs @@ -32,7 +32,7 @@ fn main() { // we need to revparse because the git dir could be anywhere if you are // using detached worktrees but there is no good way to obtain an OsString // from command output so for now we can't accept non-utf8 paths here - // probably rare enouch where it doesn't matter tough we could use gitoxide + // probably rare enough where it doesn't matter though we could use gitoxide // here but that would be make it a hard dependency and slow compile times let Some(git_dir): Option = Command::new("git") .args(["rev-parse", "--git-dir"]) diff --git a/helix-loader/src/grammar.rs b/helix-loader/src/grammar.rs index 66111aebb829..02b2a1b3d7be 100644 --- a/helix-loader/src/grammar.rs +++ b/helix-loader/src/grammar.rs @@ -8,6 +8,7 @@ use std::{ process::Command, sync::mpsc::channel, }; +#[cfg(not(target_arch = "wasm32"))] use tempfile::TempPath; use tree_sitter::Language; @@ -60,9 +61,24 @@ pub enum GrammarSource { const BUILD_TARGET: &str = env!("BUILD_TARGET"); const REMOTE_NAME: &str = "origin"; +#[cfg(target_arch = "wasm32")] +extern "C" { + fn tree_sitter_c() -> Language; + fn tree_sitter_java() -> Language; + fn tree_sitter_regex() -> Language; + fn tree_sitter_rust() -> Language; + fn tree_sitter_toml() -> Language; +} #[cfg(target_arch = "wasm32")] pub fn get_language(name: &str) -> Result { - unimplemented!() + match name { + "c" => Ok(unsafe { tree_sitter_c() }), + "java" => Ok(unsafe { tree_sitter_java() }), + "regex" => Ok(unsafe { tree_sitter_regex() }), + "rust" => Ok(unsafe { tree_sitter_rust() }), + "toml" => Ok(unsafe { tree_sitter_toml() }), + _ => Err(anyhow::anyhow!("{} is not supported", name)), + } } #[cfg(not(target_arch = "wasm32"))] @@ -85,6 +101,7 @@ pub fn get_language(name: &str) -> Result { Ok(language) } +#[cfg(not(target_arch = "wasm32"))] fn ensure_git_is_available() -> Result<()> { match which::which("git") { Ok(_cmd) => Ok(()), @@ -92,12 +109,16 @@ fn ensure_git_is_available() -> Result<()> { } } -pub fn fetch_grammars() -> Result<()> { +#[cfg(not(target_arch = "wasm32"))] +pub fn fetch_grammars(white_list: Option<&Vec>) -> Result<()> { ensure_git_is_available()?; // We do not need to fetch local grammars. let mut grammars = get_grammar_configs()?; grammars.retain(|grammar| !matches!(grammar.source, GrammarSource::Local { .. })); + if let Some(white_list) = white_list { + grammars.retain(|grammar| white_list.contains(&grammar.grammar_id)); + } println!("Fetching {} grammars", grammars.len()); let results = run_parallel(grammars, fetch_grammar); @@ -153,6 +174,7 @@ pub fn fetch_grammars() -> Result<()> { Ok(()) } +#[cfg(not(target_arch = "wasm32"))] pub fn build_grammars(target: Option) -> Result<()> { ensure_git_is_available()?; @@ -222,6 +244,7 @@ fn get_grammar_configs() -> Result> { Ok(grammars) } +#[cfg(not(target_arch = "wasm32"))] fn run_parallel(grammars: Vec, job: F) -> Vec<(String, Result)> where F: Fn(GrammarConfiguration) -> Result + Send + 'static + Clone, @@ -348,6 +371,7 @@ enum BuildStatus { Built, } +#[cfg(not(target_arch = "wasm32"))] fn build_grammar(grammar: GrammarConfiguration, target: Option<&str>) -> Result { let grammar_dir = if let GrammarSource::Local { path } = &grammar.source { PathBuf::from(&path) @@ -386,6 +410,7 @@ fn build_grammar(grammar: GrammarConfiguration, target: Option<&str>) -> Result< build_tree_sitter_library(&path, grammar, target) } +#[cfg(not(target_arch = "wasm32"))] fn build_tree_sitter_library( src_path: &Path, grammar: GrammarConfiguration, diff --git a/helix-loader/src/lib.rs b/helix-loader/src/lib.rs index 5337d6027877..b3fea42f58f0 100644 --- a/helix-loader/src/lib.rs +++ b/helix-loader/src/lib.rs @@ -1,6 +1,7 @@ pub mod config; pub mod grammar; +#[cfg(not(target_arch = "wasm32"))] use etcetera::base_strategy::{choose_base_strategy, BaseStrategy}; use std::path::{Path, PathBuf}; use std::sync::RwLock; @@ -24,9 +25,12 @@ pub fn current_working_dir() -> PathBuf { return path.clone(); } + #[cfg(not(target_arch = "wasm32"))] let path = std::env::current_dir() .and_then(dunce::canonicalize) .expect("Couldn't determine current working directory"); + #[cfg(target_arch = "wasm32")] + let path = Path::new("").to_path_buf(); let mut cwd = CWD.write().unwrap(); *cwd = Some(path.clone()); @@ -92,12 +96,13 @@ fn prioritize_runtime_dirs() -> Vec { // fallback to location of the executable being run // canonicalize the path in case the executable is symlinked - let exe_rt_dir = std::env::current_exe() + if let Some(exe_rt_dir) = std::env::current_exe() .ok() .and_then(|path| std::fs::canonicalize(path).ok()) .and_then(|path| path.parent().map(|path| path.to_path_buf().join(RT_DIR))) - .unwrap(); - rt_dirs.push(exe_rt_dir); + { + rt_dirs.push(exe_rt_dir); + } rt_dirs } @@ -141,6 +146,12 @@ pub fn runtime_file(rel_path: &Path) -> PathBuf { }) } +#[cfg(target_arch = "wasm32")] +pub fn config_dir() -> PathBuf { + ".config/helix/".into() +} + +#[cfg(not(target_arch = "wasm32"))] pub fn config_dir() -> PathBuf { // TODO: allow env var override let strategy = choose_base_strategy().expect("Unable to find the config directory!"); @@ -149,6 +160,12 @@ pub fn config_dir() -> PathBuf { path } +#[cfg(target_arch = "wasm32")] +pub fn cache_dir() -> PathBuf { + ".cache/helix/".into() +} + +#[cfg(not(target_arch = "wasm32"))] pub fn cache_dir() -> PathBuf { // TODO: allow env var override let strategy = choose_base_strategy().expect("Unable to find the config directory!"); diff --git a/helix-loader/src/main.rs b/helix-loader/src/main.rs index c1ce45ffa7cd..efda7b3de1da 100644 --- a/helix-loader/src/main.rs +++ b/helix-loader/src/main.rs @@ -5,5 +5,5 @@ use helix_loader::grammar::fetch_grammars; // compilation time. This is not meant to be run manually. fn main() -> Result<()> { - fetch_grammars() + fetch_grammars(None) } diff --git a/helix-lsp/Cargo.toml b/helix-lsp/Cargo.toml index 0b181a845474..eda4e391520d 100644 --- a/helix-lsp/Cargo.toml +++ b/helix-lsp/Cargo.toml @@ -18,14 +18,28 @@ helix-parsec = { version = "0.6", path = "../helix-parsec" } anyhow = "1.0" futures-executor = "0.3" -futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false } +futures-util = { version = "0.3", features = [ + "std", + "async-await", +], default-features = false } globset = "0.4.13" log = "0.4" lsp-types = { version = "0.94" } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" thiserror = "1.0" -tokio = { version = "1.33", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot", "sync"] } +tokio = { version = "1.33", features = [ + "rt", + "rt-multi-thread", + "io-util", + "io-std", + "time", + "process", + "macros", + "fs", + "parking_lot", + "sync", +] } tokio-stream = "0.1.14" which = "4.4" parking_lot = "0.12.1" diff --git a/helix-term/Cargo.toml b/helix-term/Cargo.toml index 72ddf360caed..50f45d2c6497 100644 --- a/helix-term/Cargo.toml +++ b/helix-term/Cargo.toml @@ -10,10 +10,12 @@ repository = "https://github.com/helix-editor/helix" homepage = "https://helix-editor.com" include = ["src/**/*", "README.md"] default-run = "hx" -rust-version = "1.65" +rust-version = "1.66" [features] -default = ["git"] +default = ["vcs", "git", "dap_lsp"] +dap_lsp = ["dep:helix-lsp", "dep:helix-dap"] +vcs = ["helix-vcs"] unicode-lines = ["helix-core/unicode-lines"] integration = [] git = ["helix-vcs/git"] @@ -25,23 +27,16 @@ path = "src/main.rs" [dependencies] helix-core = { version = "0.6", path = "../helix-core" } helix-event = { version = "0.6", path = "../helix-event" } -helix-view = { version = "0.6", path = "../helix-view" } -helix-lsp = { version = "0.6", path = "../helix-lsp" } -helix-dap = { version = "0.6", path = "../helix-dap" } -helix-vcs = { version = "0.6", path = "../helix-vcs" } +helix-lsp = { version = "0.6", path = "../helix-lsp", optional = true } +helix-dap = { version = "0.6", path = "../helix-dap", optional = true } +helix-vcs = { version = "0.6", path = "../helix-vcs", optional = true, default-features = true } helix-loader = { version = "0.6", path = "../helix-loader" } +futures-executor = "0.3" anyhow = "1" once_cell = "1.18" -which = "4.4" - -tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot"] } -tui = { path = "../helix-tui", package = "helix-tui", default-features = false, features = ["crossterm"] } -crossterm = { version = "0.27", features = ["event-stream"] } -signal-hook = "0.3" -tokio-stream = "0.1" -futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false } +crossterm.workspace = true arc-swap = { version = "1.6.0" } # Logging @@ -67,12 +62,46 @@ serde = { version = "1.0", features = ["derive"] } grep-regex = "0.1.11" grep-searcher = "0.1.11" -[target.'cfg(not(windows))'.dependencies] # https://github.com/vorner/signal-hook/issues/100 +[target.'cfg(target_family = "wasm")'.dependencies] +helix-view = { version = "0.6", path = "../helix-view", default-features = false } +tui = { path = "../helix-tui", package = "helix-tui", default-features = false } +tokio = { version = "1", features = ["rt", "io-util", "time", "macros"] } +instant = { version = "0.1", features = ["wasm-bindgen"] } +futures-util = { version = "0.3", features = [ + "async-await", +], default-features = false } + +[target.'cfg(not(target_family = "wasm"))'.dependencies] +helix-view = { version = "0.6", path = "../helix-view", default-features = true } +tui = { path = "../helix-tui", package = "helix-tui", default-features = true } +tokio = { version = "1", features = [ + "rt", + "rt-multi-thread", + "io-util", + "io-std", + "time", + "process", + "macros", + "fs", + "parking_lot", +] } +which = "4.4" +signal-hook = "0.3" +tokio-stream = "0.1" +futures-util = { version = "0.3", features = [ + "std", + "async-await", +], default-features = false } + +[target.'cfg(not(any(windows, target_family = "wasm")))'.dependencies] # https://github.com/vorner/signal-hook/issues/100 signal-hook-tokio = { version = "0.3", features = ["futures-v0_3"] } libc = "0.2.149" -[target.'cfg(target_os = "macos")'.dependencies] -crossterm = { version = "0.27", features = ["event-stream", "use-dev-tty"] } +# TODO(wasm32) restore +# [target.'cfg(target_os = "macos")'.dependencies] +# crossterm = { version = "0.27", features = ["event-stream", "use-dev-tty"] } + +[target.'cfg(not(target_os = "macos"))'.dependencies] [build-dependencies] helix-loader = { version = "0.6", path = "../helix-loader" } diff --git a/helix-term/build.rs b/helix-term/build.rs index b47dae8ef653..7c32aae20ad7 100644 --- a/helix-term/build.rs +++ b/helix-term/build.rs @@ -1,9 +1,13 @@ use helix_loader::grammar::{build_grammars, fetch_grammars}; -fn main() { - if std::env::var("HELIX_DISABLE_AUTO_GRAMMAR_BUILD").is_err() { - fetch_grammars().expect("Failed to fetch tree-sitter grammars"); - build_grammars(Some(std::env::var("TARGET").unwrap())) - .expect("Failed to compile tree-sitter grammars"); +fn main() -> std::io::Result<()> { + let wasm_build = std::matches!(std::env::var("CARGO_CFG_TARGET_ARCH"), Ok(s) if s == "wasm32"); + if !wasm_build { + fetch_grammars(None).expect("Failed to fetch tree-sitter grammars"); + if std::env::var("HELIX_DISABLE_AUTO_GRAMMAR_BUILD").is_err() { + build_grammars(Some(std::env::var("TARGET").unwrap())) + .expect("Failed to compile tree-sitter grammars"); + } } + Ok(()) } diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 43e1cdfc9eb0..047588c9b893 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -5,6 +5,7 @@ use helix_core::{ path::get_relative_path, pos_at_coords, syntax, Selection, }; +#[cfg(feature = "dap_lsp")] use helix_lsp::{ lsp::{self, notification::Notification}, util::lsp_pos_to_pos, @@ -22,9 +23,10 @@ use helix_view::{ use serde_json::json; use tui::backend::Backend; +#[cfg(feature = "dap_lsp")] +use crate::commands::apply_workspace_edit; use crate::{ args::Args, - commands::apply_workspace_edit, compositor::{Compositor, Event}, config::Config, job::Jobs, @@ -33,16 +35,17 @@ use crate::{ }; use log::{debug, error, warn}; -#[cfg(not(feature = "integration"))] -use std::io::stdout; use std::{collections::btree_map::Entry, io::stdin, path::Path, sync::Arc}; use anyhow::{Context, Error}; -use crossterm::{event::Event as CrosstermEvent, tty::IsTty}; -#[cfg(not(windows))] +use crossterm::event::Event as CrosstermEvent; +#[cfg(not(target_arch = "wasm32"))] +use crossterm::tty::IsTty; + +#[cfg(not(any(windows, target_arch = "wasm32")))] use {signal_hook::consts::signal, signal_hook_tokio::Signals}; -#[cfg(windows)] +#[cfg(any(windows, target_arch = "wasm32"))] type Signals = futures_util::stream::Empty<()>; #[cfg(not(feature = "integration"))] @@ -52,16 +55,36 @@ use tui::backend::CrosstermBackend; use tui::backend::TestBackend; #[cfg(not(feature = "integration"))] -type TerminalBackend = CrosstermBackend; +type TerminalBackend = CrosstermBackend; #[cfg(feature = "integration")] type TerminalBackend = TestBackend; -type Terminal = tui::terminal::Terminal; +#[cfg(not(feature = "integration"))] +type Terminal = tui::terminal::Terminal>; + +#[cfg(not(feature = "integration"))] +use tui::backend::Buffer; + +#[cfg(feature = "integration")] +type Terminal = tui::terminal::Terminal; -pub struct Application { +#[cfg(feature = "integration")] +pub trait Buffer {} +#[cfg(feature = "integration")] +impl Buffer for T {} + +pub struct Application +where + B: Buffer, +{ compositor: Compositor, + #[cfg(not(feature = "integration"))] + terminal: Terminal, + #[cfg(feature = "integration")] terminal: Terminal, + #[cfg(feature = "integration")] + phantom_data: std::marker::PhantomData, pub editor: Editor, config: Arc>, @@ -73,6 +96,7 @@ pub struct Application { signals: Signals, jobs: Jobs, + #[cfg(feature = "dap_lsp")] lsp_progress: LspProgressMap, } @@ -98,12 +122,35 @@ fn setup_integration_logging() { .apply(); } -impl Application { +#[cfg(not(target_arch = "wasm32"))] +impl Application { pub fn new( args: Args, config: Config, syn_loader_conf: syntax::Configuration, - ) -> Result { + ) -> Result, Error> { + Ok(Application::new_with_write( + args, + config, + syn_loader_conf, + std::io::stdout(), + )?) + } +} + +impl Application +where + B: Buffer, +{ + pub fn new_with_write( + args: Args, + config: Config, + syn_loader_conf: syntax::Configuration, + buffer: B, + ) -> Result + where + B: Buffer, + { #[cfg(feature = "integration")] setup_integration_logging(); @@ -132,7 +179,7 @@ impl Application { let syn_loader = std::sync::Arc::new(syntax::Loader::new(syn_loader_conf)); #[cfg(not(feature = "integration"))] - let backend = CrosstermBackend::new(stdout(), &config.editor); + let backend = CrosstermBackend::new(buffer, &config.editor); #[cfg(feature = "integration")] let backend = TestBackend::new(120, 150); @@ -162,6 +209,7 @@ impl Application { // Unset path to prevent accidentally saving to the original tutor file. doc_mut!(editor).set_path(None); } else if !args.files.is_empty() { + #[cfg(not(target_arch = "wasm32"))] if args.open_cwd { // NOTE: The working directory is already set to args.files[0] in main() editor.new_file(Action::VerticalSplit); @@ -209,19 +257,24 @@ impl Application { let (view, doc) = current!(editor); align_view(doc, view, Align::Center); } - } else if stdin().is_tty() || cfg!(feature = "integration") { - editor.new_file(Action::VerticalSplit); } else { - editor - .new_file_from_stdin(Action::VerticalSplit) - .unwrap_or_else(|_| editor.new_file(Action::VerticalSplit)); + #[cfg(not(target_arch = "wasm32"))] + if stdin().is_tty() || cfg!(feature = "integration") { + editor.new_file(Action::VerticalSplit); + } else { + editor + .new_file_from_stdin(Action::VerticalSplit) + .unwrap_or_else(|_| editor.new_file(Action::VerticalSplit)); + } + #[cfg(target_arch = "wasm32")] + editor.new_file(Action::VerticalSplit); } editor.set_theme(theme); - #[cfg(windows)] + #[cfg(any(windows, target_arch = "wasm32"))] let signals = futures_util::stream::empty(); - #[cfg(not(windows))] + #[cfg(not(any(windows, target_arch = "wasm32")))] let signals = Signals::new([ signal::SIGTSTP, signal::SIGCONT, @@ -234,6 +287,8 @@ impl Application { let app = Self { compositor, terminal, + #[cfg(feature = "integration")] + phantom_data: std::marker::PhantomData, editor, config, @@ -243,6 +298,7 @@ impl Application { signals, jobs: Jobs::new(), + #[cfg(feature = "dap_lsp")] lsp_progress: LspProgressMap::new(), }; @@ -282,9 +338,9 @@ impl Application { self.terminal.draw(pos, kind).unwrap(); } - pub async fn event_loop(&mut self, input_stream: &mut S) + pub async fn event_loop(&mut self, input_stream: &mut IS) where - S: Stream> + Unpin, + IS: Stream> + Unpin, { self.render().await; @@ -295,9 +351,9 @@ impl Application { } } - pub async fn event_loop_until_idle(&mut self, input_stream: &mut S) -> bool + pub async fn event_loop_until_idle(&mut self, input_stream: &mut IS) -> bool where - S: Stream> + Unpin, + IS: Stream> + Unpin, { loop { if self.editor.should_close() { @@ -310,22 +366,27 @@ impl Application { biased; Some(signal) = self.signals.next() => { + debug!("handling signal"); if !self.handle_signals(signal).await { return false; }; } Some(event) = input_stream.next() => { + debug!("handling input"); self.handle_terminal_events(event).await; } Some(callback) = self.jobs.futures.next() => { + debug!("handling future"); self.jobs.handle_callback(&mut self.editor, &mut self.compositor, callback); self.render().await; } Some(callback) = self.jobs.wait_futures.next() => { + debug!("handling wait future"); self.jobs.handle_callback(&mut self.editor, &mut self.compositor, callback); self.render().await; } event = self.editor.wait_event() => { + debug!("handling wait event"); let _idle_handled = self.handle_editor_event(event).await; #[cfg(feature = "integration")] @@ -434,13 +495,13 @@ impl Application { } } - #[cfg(windows)] + #[cfg(any(windows, target_arch = "wasm32"))] // no signal handling available on windows pub async fn handle_signals(&mut self, _signal: ()) -> bool { true } - #[cfg(not(windows))] + #[cfg(not(any(windows, target_arch = "wasm32")))] pub async fn handle_signals(&mut self, signal: i32) -> bool { match signal { signal::SIGTSTP => { @@ -558,6 +619,7 @@ impl Application { let doc = doc_mut!(self.editor, &doc_save_event.doc_id); let id = doc.id(); doc.detect_language(loader); + #[cfg(feature = "dap_lsp")] self.editor.refresh_language_servers(id); } @@ -583,11 +645,13 @@ impl Application { self.handle_config_events(event); self.render().await; } + #[cfg(feature = "dap_lsp")] EditorEvent::LanguageServerMessage((id, call)) => { self.handle_language_server_message(call, id).await; // limit render calls for fast language server messages helix_event::request_redraw(); } + #[cfg(feature = "dap_lsp")] EditorEvent::DebuggerEvent(payload) => { let needs_render = self.editor.handle_debugger_message(payload).await; if needs_render { @@ -644,6 +708,7 @@ impl Application { } } + #[cfg(feature = "dap_lsp")] pub async fn handle_language_server_message( &mut self, call: helix_lsp::Call, @@ -1150,9 +1215,9 @@ impl Application { self.terminal.restore(terminal_config) } - pub async fn run(&mut self, input_stream: &mut S) -> Result + pub async fn run(&mut self, input_stream: &mut IS) -> Result where - S: Stream> + Unpin, + IS: Stream> + Unpin, { self.claim_term().await?; @@ -1162,6 +1227,9 @@ impl Application { // We can't handle errors properly inside this closure. And it's // probably not a good idea to `unwrap()` inside a panic handler. // So we just ignore the `Result`. + #[cfg(not(feature = "integration"))] + let _ = TerminalBackend::::force_restore(); + #[cfg(feature = "integration")] let _ = TerminalBackend::force_restore(); hook(info); })); @@ -1200,6 +1268,7 @@ impl Application { errs.push(err); } + #[cfg(feature = "dap_lsp")] if self.editor.close_language_servers(None).await.is_err() { log::error!("Timed out waiting for language servers to shutdown"); errs.push(anyhow::format_err!( diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 75df430a3eb1..e63d9a0b8cc1 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -1,9 +1,14 @@ +#[cfg(feature = "dap_lsp")] pub(crate) mod dap; +#[cfg(feature = "dap_lsp")] pub(crate) mod lsp; pub(crate) mod typed; +#[cfg(feature = "dap_lsp")] pub use dap::*; +#[cfg(feature = "vcs")] use helix_vcs::Hunk; +#[cfg(feature = "dap_lsp")] pub use lsp::*; use tokio::sync::oneshot; use tui::widgets::Row; @@ -46,6 +51,8 @@ use anyhow::{anyhow, bail, ensure, Context as _}; use insert::*; use movement::Movement; +#[cfg(feature = "dap_lsp")] +use crate::ui::CompletionItem; use crate::{ args, compositor::{self, Component, Compositor}, @@ -53,8 +60,8 @@ use crate::{ job::Callback, keymap::ReverseKeymap, ui::{ - self, editor::InsertEvent, lsp::SignatureHelp, overlay::overlaid, CompletionItem, Picker, - Popup, Prompt, PromptEvent, + self, editor::InsertEvent, lsp::SignatureHelp, overlay::overlaid, Picker, Popup, Prompt, + PromptEvent, }, }; @@ -110,6 +117,7 @@ impl<'a> Context<'a> { self.on_next_key_callback = Some(Box::new(on_next_key_callback)); } + #[cfg(feature = "dap_lsp")] #[inline] pub fn callback( &mut self, @@ -129,6 +137,7 @@ impl<'a> Context<'a> { } } +#[cfg(feature = "dap_lsp")] #[inline] fn make_job_callback( call: impl Future> + 'static + Send, @@ -170,8 +179,9 @@ pub enum MappableCommand { } macro_rules! static_commands { - ( $($name:ident, $doc:literal,)* ) => { + ( $($(#[cfg($attr:meta)])? $name:ident, $doc:literal,)* ) => { $( + $(#[cfg($attr)])? #[allow(non_upper_case_globals)] pub const $name: Self = Self::Static { name: stringify!($name), @@ -181,7 +191,7 @@ macro_rules! static_commands { )* pub const STATIC_COMMAND_LIST: &'static [Self] = &[ - $( Self::$name, )* + $( $(#[cfg($attr)])? Self::$name, )* ]; } } @@ -288,6 +298,7 @@ impl MappableCommand { extend_search_prev, "Add previous search match to selection", search_selection, "Use current selection as search pattern", make_search_word_bounded, "Modify current search to make it word bounded", + #[cfg(not(target_arch = "wasm32"))] global_search, "Global search in workspace folder", extend_line, "Select current line, if already selected, extend to another line based on the anchor", extend_line_below, "Select current line, if already selected, extend to next line", @@ -304,17 +315,29 @@ impl MappableCommand { insert_mode, "Insert before selection", append_mode, "Append after selection", command_mode, "Enter command mode", + #[cfg(not(target_arch = "wasm32"))] file_picker, "Open file picker", + #[cfg(not(target_arch = "wasm32"))] file_picker_in_current_buffer_directory, "Open file picker at current buffers's directory", + #[cfg(not(target_arch = "wasm32"))] file_picker_in_current_directory, "Open file picker at current working directory", + #[cfg(feature = "dap_lsp")] code_action, "Perform code action", + #[cfg(not(target_arch = "wasm32"))] buffer_picker, "Open buffer picker", + #[cfg(not(target_arch = "wasm32"))] jumplist_picker, "Open jumplist picker", + #[cfg(feature = "dap_lsp")] symbol_picker, "Open symbol picker", + #[cfg(feature = "dap_lsp")] select_references_to_symbol_under_cursor, "Select symbol references", + #[cfg(feature = "dap_lsp")] workspace_symbol_picker, "Open workspace symbol picker", + #[cfg(feature = "dap_lsp")] diagnostics_picker, "Open diagnostic picker", + #[cfg(feature = "dap_lsp")] workspace_diagnostics_picker, "Open workspace diagnostic picker", + #[cfg(not(target_arch = "wasm32"))] last_picker, "Open last picker", insert_at_line_start, "Insert at start of line", insert_at_line_end, "Insert at end of line", @@ -323,17 +346,25 @@ impl MappableCommand { normal_mode, "Enter normal mode", select_mode, "Enter selection extend mode", exit_select_mode, "Exit selection mode", + #[cfg(feature = "dap_lsp")] goto_definition, "Goto definition", + #[cfg(feature = "dap_lsp")] goto_declaration, "Goto declaration", add_newline_above, "Add newline above", add_newline_below, "Add newline below", + #[cfg(feature = "dap_lsp")] goto_type_definition, "Goto type definition", + #[cfg(feature = "dap_lsp")] goto_implementation, "Goto implementation", goto_file_start, "Goto line number else file start", goto_file_end, "Goto file end", + #[cfg(not(target_arch = "wasm32"))] goto_file, "Goto files in selection", + #[cfg(not(target_arch = "wasm32"))] goto_file_hsplit, "Goto files in selection (hsplit)", + #[cfg(not(target_arch = "wasm32"))] goto_file_vsplit, "Goto files in selection (vsplit)", + #[cfg(feature = "dap_lsp")] goto_reference, "Goto references", goto_window_top, "Goto window top", goto_window_center, "Goto window center", @@ -343,13 +374,21 @@ impl MappableCommand { goto_last_modification, "Goto last modification", goto_line, "Goto line", goto_last_line, "Goto last line", + #[cfg(feature = "dap_lsp")] goto_first_diag, "Goto first diagnostic", + #[cfg(feature = "dap_lsp")] goto_last_diag, "Goto last diagnostic", + #[cfg(feature = "dap_lsp")] goto_next_diag, "Goto next diagnostic", + #[cfg(feature = "dap_lsp")] goto_prev_diag, "Goto previous diagnostic", + #[cfg(feature = "vcs")] goto_next_change, "Goto next change", + #[cfg(feature = "vcs")] goto_prev_change, "Goto previous change", + #[cfg(feature = "vcs")] goto_first_change, "Goto first change", + #[cfg(feature = "vcs")] goto_last_change, "Goto last change", goto_line_start, "Goto line start", goto_line_end, "Goto line end", @@ -362,6 +401,7 @@ impl MappableCommand { extend_to_first_nonwhitespace, "Extend to first non-blank in line", extend_to_line_end, "Extend to line end", extend_to_line_end_newline, "Extend to line end", + #[cfg(feature = "dap_lsp")] signature_help, "Show signature help", smart_tab, "Insert tab if all cursors have all whitespace to their left; otherwise, run a separate command.", insert_tab, "Insert tab char", @@ -396,6 +436,7 @@ impl MappableCommand { paste_primary_clipboard_before, "Paste primary clipboard before selections", indent, "Indent selection", unindent, "Unindent selection", + #[cfg(feature = "dap_lsp")] format_selections, "Format selection", join_selections, "Join lines inside selection", join_selections_space, "Join lines inside selection and select spaces", @@ -404,7 +445,9 @@ impl MappableCommand { align_selections, "Align selections in column", keep_primary_selection, "Keep primary selection", remove_primary_selection, "Remove primary selection", + #[cfg(feature = "dap_lsp")] completion, "Invoke completion popup", + #[cfg(feature = "dap_lsp")] hover, "Show docs for item under cursor", toggle_comments, "Comment/uncomment selections", rotate_selections_forward, "Rotate selections forward", @@ -462,28 +505,50 @@ impl MappableCommand { goto_prev_test, "Goto previous test", goto_next_paragraph, "Goto next paragraph", goto_prev_paragraph, "Goto previous paragraph", + #[cfg(feature = "dap_lsp")] dap_launch, "Launch debug target", + #[cfg(feature = "dap_lsp")] dap_restart, "Restart debugging session", + #[cfg(feature = "dap_lsp")] dap_toggle_breakpoint, "Toggle breakpoint", + #[cfg(feature = "dap_lsp")] dap_continue, "Continue program execution", + #[cfg(feature = "dap_lsp")] dap_pause, "Pause program execution", + #[cfg(feature = "dap_lsp")] dap_step_in, "Step in", + #[cfg(feature = "dap_lsp")] dap_step_out, "Step out", + #[cfg(feature = "dap_lsp")] dap_next, "Step to next", + #[cfg(feature = "dap_lsp")] dap_variables, "List variables", + #[cfg(feature = "dap_lsp")] dap_terminate, "End debug session", + #[cfg(feature = "dap_lsp")] dap_edit_condition, "Edit breakpoint condition on current line", + #[cfg(feature = "dap_lsp")] dap_edit_log, "Edit breakpoint log message on current line", + #[cfg(feature = "dap_lsp")] dap_switch_thread, "Switch current thread", + #[cfg(feature = "dap_lsp")] dap_switch_stack_frame, "Switch stack frame", + #[cfg(feature = "dap_lsp")] dap_enable_exceptions, "Enable exception breakpoints", + #[cfg(feature = "dap_lsp")] dap_disable_exceptions, "Disable exception breakpoints", + #[cfg(not(target_arch = "wasm32"))] shell_pipe, "Pipe selections through shell command", + #[cfg(not(target_arch = "wasm32"))] shell_pipe_to, "Pipe selections into shell command ignoring output", + #[cfg(not(target_arch = "wasm32"))] shell_insert_output, "Insert shell command output before selections", + #[cfg(not(target_arch = "wasm32"))] shell_append_output, "Append shell command output after selections", + #[cfg(not(target_arch = "wasm32"))] shell_keep_pipe, "Filter selections with shell predicate", suspend, "Suspend and return to shell", + #[cfg(feature = "dap_lsp")] rename_symbol, "Rename symbol", increment, "Increment item under cursor", decrement, "Decrement item under cursor", @@ -1141,18 +1206,22 @@ fn goto_file_end(cx: &mut Context) { doc.set_selection(view.id, selection); } +#[cfg(not(target_arch = "wasm32"))] fn goto_file(cx: &mut Context) { goto_file_impl(cx, Action::Replace); } +#[cfg(not(target_arch = "wasm32"))] fn goto_file_hsplit(cx: &mut Context) { goto_file_impl(cx, Action::HorizontalSplit); } +#[cfg(not(target_arch = "wasm32"))] fn goto_file_vsplit(cx: &mut Context) { goto_file_impl(cx, Action::VerticalSplit); } +#[cfg(not(target_arch = "wasm32"))] /// Goto files in selection. fn goto_file_impl(cx: &mut Context, action: Action) { let (view, doc) = current_ref!(cx.editor); @@ -2095,6 +2164,7 @@ fn make_search_word_bounded(cx: &mut Context) { } } +#[cfg(not(target_arch = "wasm32"))] fn global_search(cx: &mut Context) { #[derive(Debug)] struct FileResult { @@ -2538,6 +2608,7 @@ fn delete_by_selection_insert_mode( ); } doc.apply(&transaction, view.id); + #[cfg(feature = "dap_lsp")] lsp::signature_help_impl(cx, SignatureHelpInvoked::Automatic); } @@ -2650,6 +2721,7 @@ fn append_mode(cx: &mut Context) { doc.set_selection(view.id, selection); } +#[cfg(not(target_arch = "wasm32"))] fn file_picker(cx: &mut Context) { let root = find_workspace().0; if !root.exists() { @@ -2660,6 +2732,7 @@ fn file_picker(cx: &mut Context) { cx.push_layer(Box::new(overlaid(picker))); } +#[cfg(not(target_arch = "wasm32"))] fn file_picker_in_current_buffer_directory(cx: &mut Context) { let doc_dir = doc!(cx.editor) .path() @@ -2677,6 +2750,7 @@ fn file_picker_in_current_buffer_directory(cx: &mut Context) { cx.push_layer(Box::new(overlaid(picker))); } +#[cfg(not(target_arch = "wasm32"))] fn file_picker_in_current_directory(cx: &mut Context) { let cwd = helix_loader::current_working_dir(); if !cwd.exists() { @@ -2688,15 +2762,20 @@ fn file_picker_in_current_directory(cx: &mut Context) { cx.push_layer(Box::new(overlaid(picker))); } +#[cfg(not(target_arch = "wasm32"))] fn buffer_picker(cx: &mut Context) { let current = view!(cx.editor).doc; + #[cfg(target_arch = "wasm32")] + use instant::Instant; + #[cfg(not(target_arch = "wasm32"))] + use std::time::Instant; struct BufferMeta { id: DocumentId, path: Option, is_modified: bool, is_current: bool, - focused_at: std::time::Instant, + focused_at: Instant, } impl ui::menu::Item for BufferMeta { @@ -2757,6 +2836,7 @@ fn buffer_picker(cx: &mut Context) { cx.push_layer(Box::new(overlaid(picker))); } +#[cfg(not(target_arch = "wasm32"))] fn jumplist_picker(cx: &mut Context) { struct JumpMeta { id: DocumentId, @@ -2927,6 +3007,7 @@ pub fn command_palette(cx: &mut Context) { )); } +#[cfg(not(target_arch = "wasm32"))] fn last_picker(cx: &mut Context) { // TODO: last picker does not seem to work well with buffer_picker cx.callback = Some(Box::new(|compositor, cx| { @@ -3278,6 +3359,7 @@ fn exit_select_mode(cx: &mut Context) { } } +#[cfg(feature = "dap_lsp")] fn goto_first_diag(cx: &mut Context) { let (view, doc) = current!(cx.editor); let selection = match doc.shown_diagnostics().next() { @@ -3287,6 +3369,7 @@ fn goto_first_diag(cx: &mut Context) { doc.set_selection(view.id, selection); } +#[cfg(feature = "dap_lsp")] fn goto_last_diag(cx: &mut Context) { let (view, doc) = current!(cx.editor); let selection = match doc.shown_diagnostics().last() { @@ -3296,6 +3379,7 @@ fn goto_last_diag(cx: &mut Context) { doc.set_selection(view.id, selection); } +#[cfg(feature = "dap_lsp")] fn goto_next_diag(cx: &mut Context) { let (view, doc) = current!(cx.editor); @@ -3316,6 +3400,7 @@ fn goto_next_diag(cx: &mut Context) { doc.set_selection(view.id, selection); } +#[cfg(feature = "dap_lsp")] fn goto_prev_diag(cx: &mut Context) { let (view, doc) = current!(cx.editor); @@ -3339,14 +3424,17 @@ fn goto_prev_diag(cx: &mut Context) { doc.set_selection(view.id, selection); } +#[cfg(feature = "vcs")] fn goto_first_change(cx: &mut Context) { goto_first_change_impl(cx, false); } +#[cfg(feature = "vcs")] fn goto_last_change(cx: &mut Context) { goto_first_change_impl(cx, true); } +#[cfg(feature = "vcs")] fn goto_first_change_impl(cx: &mut Context, reverse: bool) { let editor = &mut cx.editor; let (view, doc) = current!(editor); @@ -3367,14 +3455,17 @@ fn goto_first_change_impl(cx: &mut Context, reverse: bool) { } } +#[cfg(feature = "vcs")] fn goto_next_change(cx: &mut Context) { goto_next_change_impl(cx, Direction::Forward) } +#[cfg(feature = "vcs")] fn goto_prev_change(cx: &mut Context) { goto_next_change_impl(cx, Direction::Backward) } +#[cfg(feature = "vcs")] fn goto_next_change_impl(cx: &mut Context, direction: Direction) { let count = cx.count() as u32 - 1; let motion = move |editor: &mut Editor| { @@ -3422,6 +3513,7 @@ fn goto_next_change_impl(cx: &mut Context, direction: Direction) { cx.editor.apply_motion(motion); } +#[cfg(feature = "vcs")] /// Returns the [Range] for a [Hunk] in the given text. /// Additions and modifications cover the added and modified ranges. /// Deletions are represented as the point at the start of the deletion hunk. @@ -3453,8 +3545,8 @@ pub mod insert { } } - // It trigger completion when idle timer reaches deadline - // Only trigger completion if the word under cursor is longer than n characters + #[cfg(feature = "dap_lsp")] // It trigger completion when idle timer reaches deadline + // Only trigger completion if the word under cursor is longer than n characters pub fn idle_completion(cx: &mut Context) { let config = cx.editor.config(); let (view, doc) = current!(cx.editor); @@ -3473,6 +3565,7 @@ pub mod insert { super::completion(cx); } + #[cfg(feature = "dap_lsp")] fn language_server_completion(cx: &mut Context, ch: char) { let config = cx.editor.config(); if !config.auto_completion { @@ -3498,6 +3591,7 @@ pub mod insert { } } + #[cfg(feature = "dap_lsp")] fn signature_help(cx: &mut Context, ch: char) { use helix_lsp::lsp; // if ch matches signature_help char, trigger @@ -3564,6 +3658,7 @@ pub mod insert { doc.apply(&t, view.id); } + #[cfg(feature = "dap_lsp")] // TODO: need a post insert hook too for certain triggers (autocomplete, signature help, etc) // this could also generically look at Transaction, but it's a bit annoying to look at // Operation instead of Change. @@ -3794,6 +3889,7 @@ pub mod insert { let (view, doc) = current!(cx.editor); doc.apply(&transaction, view.id); + #[cfg(feature = "dap_lsp")] lsp::signature_help_impl(cx, SignatureHelpInvoked::Automatic); } @@ -4248,6 +4344,7 @@ fn unindent(cx: &mut Context) { doc.apply(&transaction, view.id); } +#[cfg(feature = "dap_lsp")] fn format_selections(cx: &mut Context) { use helix_lsp::{lsp, util::range_to_lsp_range}; @@ -4430,6 +4527,7 @@ fn remove_primary_selection(cx: &mut Context) { doc.set_selection(view.id, selection); } +#[cfg(feature = "dap_lsp")] pub fn completion(cx: &mut Context) { use helix_lsp::{lsp, util::pos_to_lsp_pos}; @@ -5141,6 +5239,7 @@ fn select_textobject(cx: &mut Context, objtype: textobject::TextObject) { return; } + #[cfg(feature = "vcs")] let textobject_change = |range: Range| -> Range { let diff_handle = doc.diff_handle().unwrap(); let diff = diff_handle.load(); @@ -5170,6 +5269,7 @@ fn select_textobject(cx: &mut Context, objtype: textobject::TextObject) { 'm' => textobject::textobject_pair_surround_closest( text, range, objtype, count, ), + #[cfg(feature = "vcs")] 'g' => textobject_change(range), // TODO: cancel new ranges if inconsistent surround matches across lines ch if !ch.is_ascii_alphanumeric() => { @@ -5334,22 +5434,27 @@ enum ShellBehavior { Append, } +#[cfg(not(target_arch = "wasm32"))] fn shell_pipe(cx: &mut Context) { shell_prompt(cx, "pipe:".into(), ShellBehavior::Replace); } +#[cfg(not(target_arch = "wasm32"))] fn shell_pipe_to(cx: &mut Context) { shell_prompt(cx, "pipe-to:".into(), ShellBehavior::Ignore); } +#[cfg(not(target_arch = "wasm32"))] fn shell_insert_output(cx: &mut Context) { shell_prompt(cx, "insert-output:".into(), ShellBehavior::Insert); } +#[cfg(not(target_arch = "wasm32"))] fn shell_append_output(cx: &mut Context) { shell_prompt(cx, "append-output:".into(), ShellBehavior::Append); } +#[cfg(not(target_arch = "wasm32"))] fn shell_keep_pipe(cx: &mut Context) { ui::prompt( cx, @@ -5402,10 +5507,12 @@ fn shell_keep_pipe(cx: &mut Context) { ); } +#[cfg(not(target_arch = "wasm32"))] fn shell_impl(shell: &[String], cmd: &str, input: Option) -> anyhow::Result<(Tendril, bool)> { - tokio::task::block_in_place(|| helix_lsp::block_on(shell_impl_async(shell, cmd, input))) + tokio::task::block_in_place(|| futures_executor::block_on(shell_impl_async(shell, cmd, input))) } +#[cfg(not(target_arch = "wasm32"))] async fn shell_impl_async( shell: &[String], cmd: &str, @@ -5476,6 +5583,7 @@ async fn shell_impl_async( Ok((tendril, output.status.success())) } +#[cfg(not(target_arch = "wasm32"))] fn shell(cx: &mut compositor::Context, cmd: &str, behavior: &ShellBehavior) { let pipe = match behavior { ShellBehavior::Replace | ShellBehavior::Ignore => true, @@ -5549,6 +5657,7 @@ fn shell(cx: &mut compositor::Context, cmd: &str, behavior: &ShellBehavior) { view.ensure_cursor_in_view(doc, config.scrolloff); } +#[cfg(not(target_arch = "wasm32"))] fn shell_prompt(cx: &mut Context, prompt: Cow<'static, str>, behavior: ShellBehavior) { ui::prompt( cx, @@ -5569,7 +5678,7 @@ fn shell_prompt(cx: &mut Context, prompt: Cow<'static, str>, behavior: ShellBeha } fn suspend(_cx: &mut Context) { - #[cfg(not(windows))] + #[cfg(not(any(windows, target_arch = "wasm32")))] signal_hook::low_level::raise(signal_hook::consts::signal::SIGTSTP).unwrap(); } diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index abe6dd97ec89..2299423a2c58 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -110,6 +110,7 @@ fn open(cx: &mut compositor::Context, args: &[Cow], event: PromptEvent) -> for arg in args { let (path, pos) = args::parse_file(arg); let path = helix_core::path::expand_tilde(&path); + #[cfg(not(target_arch = "wasm32"))] // 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()) { @@ -132,6 +133,16 @@ fn open(cx: &mut compositor::Context, args: &[Cow], event: PromptEvent) -> // does not affect opening a buffer without pos align_view(doc, view, Align::Center); } + #[cfg(target_arch = "wasm32")] + { + // just open the file + let _ = cx.editor.open(&path, Action::Replace)?; + let (view, doc) = current!(cx.editor); + let pos = Selection::point(pos_at_coords(doc.text().slice(..), pos, true)); + doc.set_selection(view.id, pos); + // does not affect opening a buffer without pos + align_view(doc, view, Align::Center); + } } Ok(()) } @@ -339,6 +350,7 @@ fn write_impl( insert_final_newline(doc, view); } + #[cfg(feature = "dap_lsp")] let fmt = if config.auto_format { doc.auto_format().map(|fmt| { let callback = make_format_callback( @@ -354,6 +366,8 @@ fn write_impl( } else { None }; + #[cfg(not(feature = "dap_lsp"))] + let fmt: Option<()> = None; if fmt.is_none() { let id = doc.id(); @@ -441,6 +455,7 @@ fn new_file( Ok(()) } +#[cfg(feature = "dap_lsp")] fn format( cx: &mut compositor::Context, _args: &[Cow], @@ -717,6 +732,7 @@ pub fn write_all_impl( insert_final_newline(doc, view_mut!(cx.editor, target_view)); } + #[cfg(feature = "dap_lsp")] let fmt = if config.auto_format { doc.auto_format().map(|fmt| { let callback = make_format_callback( @@ -731,6 +747,8 @@ pub fn write_all_impl( } else { None }; + #[cfg(not(feature = "dap_lsp"))] + let fmt: Option<()> = None; if fmt.is_none() { cx.editor.save::(doc_id, None, force)?; @@ -1283,9 +1301,12 @@ fn reload( let scrolloff = cx.editor.config().scrolloff; let (view, doc) = current!(cx.editor); + + #[cfg(feature = "vcs")] doc.reload(view, &cx.editor.diff_providers).map(|_| { view.ensure_cursor_in_view(doc, scrolloff); })?; + #[cfg(feature = "dap_lsp")] if let Some(path) = doc.path() { cx.editor .language_servers @@ -1331,7 +1352,9 @@ fn reload_all( // Ensure that the view is synced with the document's history. view.sync_changes(doc); + #[cfg(feature = "vcs")] doc.reload(view, &cx.editor.diff_providers)?; + #[cfg(feature = "dap_lsp")] if let Some(path) = doc.path() { cx.editor .language_servers @@ -1368,6 +1391,7 @@ fn update( } } +#[cfg(feature = "dap_lsp")] fn lsp_workspace_command( cx: &mut compositor::Context, args: &[Cow], @@ -1435,6 +1459,7 @@ fn lsp_workspace_command( Ok(()) } +#[cfg(feature = "dap_lsp")] fn lsp_restart( cx: &mut compositor::Context, _args: &[Cow], @@ -1483,6 +1508,7 @@ fn lsp_restart( Ok(()) } +#[cfg(feature = "dap_lsp")] fn lsp_stop( cx: &mut compositor::Context, _args: &[Cow], @@ -1691,6 +1717,7 @@ fn hsplit_new( Ok(()) } +#[cfg(feature = "dap_lsp")] fn debug_eval( cx: &mut compositor::Context, args: &[Cow], @@ -1717,6 +1744,7 @@ fn debug_eval( Ok(()) } +#[cfg(feature = "dap_lsp")] fn debug_start( cx: &mut compositor::Context, args: &[Cow], @@ -1734,6 +1762,7 @@ fn debug_start( dap_start_impl(cx, name.as_deref(), None, Some(args)) } +#[cfg(feature = "dap_lsp")] fn debug_remote( cx: &mut compositor::Context, args: &[Cow], @@ -2007,6 +2036,7 @@ fn language( doc.detect_indent_and_line_ending(); let id = doc.id(); + #[cfg(feature = "dap_lsp")] cx.editor.refresh_language_servers(id); Ok(()) } @@ -2205,6 +2235,7 @@ fn refresh_config( Ok(()) } +#[cfg(not(target_arch = "wasm32"))] fn append_output( cx: &mut compositor::Context, args: &[Cow], @@ -2219,6 +2250,7 @@ fn append_output( Ok(()) } +#[cfg(not(target_arch = "wasm32"))] fn insert_output( cx: &mut compositor::Context, args: &[Cow], @@ -2233,6 +2265,7 @@ fn insert_output( Ok(()) } +#[cfg(not(target_arch = "wasm32"))] fn pipe_to( cx: &mut compositor::Context, args: &[Cow], @@ -2241,10 +2274,12 @@ fn pipe_to( pipe_impl(cx, args, event, &ShellBehavior::Ignore) } +#[cfg(not(target_arch = "wasm32"))] fn pipe(cx: &mut compositor::Context, args: &[Cow], event: PromptEvent) -> anyhow::Result<()> { pipe_impl(cx, args, event, &ShellBehavior::Replace) } +#[cfg(not(target_arch = "wasm32"))] fn pipe_impl( cx: &mut compositor::Context, args: &[Cow], @@ -2260,6 +2295,7 @@ fn pipe_impl( Ok(()) } +#[cfg(not(target_arch = "wasm32"))] fn run_shell_command( cx: &mut compositor::Context, args: &[Cow], @@ -2300,6 +2336,7 @@ fn run_shell_command( Ok(()) } +#[cfg(feature = "vcs")] fn reset_diff_change( cx: &mut compositor::Context, args: &[Cow], @@ -2521,6 +2558,7 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[ fun: new_file, signature: CommandSignature::none(), }, + #[cfg(feature = "dap_lsp")] TypableCommand { name: "format", aliases: &["fmt"], @@ -2769,6 +2807,7 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[ fun: update, signature: CommandSignature::none(), }, + #[cfg(feature = "dap_lsp")] TypableCommand { name: "lsp-workspace-command", aliases: &[], @@ -2776,6 +2815,7 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[ fun: lsp_workspace_command, signature: CommandSignature::positional(&[completers::lsp_workspace_command]), }, + #[cfg(feature = "dap_lsp")] TypableCommand { name: "lsp-restart", aliases: &[], @@ -2783,6 +2823,7 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[ fun: lsp_restart, signature: CommandSignature::none(), }, + #[cfg(feature = "dap_lsp")] TypableCommand { name: "lsp-stop", aliases: &[], @@ -2804,6 +2845,7 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[ fun: tree_sitter_highlight_name, signature: CommandSignature::none(), }, + #[cfg(feature = "dap_lsp")] TypableCommand { name: "debug-start", aliases: &["dbg"], @@ -2811,6 +2853,7 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[ fun: debug_start, signature: CommandSignature::none(), }, + #[cfg(feature = "dap_lsp")] TypableCommand { name: "debug-remote", aliases: &["dbg-tcp"], @@ -2818,6 +2861,7 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[ fun: debug_remote, signature: CommandSignature::none(), }, + #[cfg(feature = "dap_lsp")] TypableCommand { name: "debug-eval", aliases: &[], @@ -2952,6 +2996,7 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[ fun: open_log, signature: CommandSignature::none(), }, + #[cfg(not(target_arch = "wasm32"))] TypableCommand { name: "insert-output", aliases: &[], @@ -2959,6 +3004,7 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[ fun: insert_output, signature: CommandSignature::none(), }, + #[cfg(not(target_arch = "wasm32"))] TypableCommand { name: "append-output", aliases: &[], @@ -2966,6 +3012,7 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[ fun: append_output, signature: CommandSignature::none(), }, + #[cfg(not(target_arch = "wasm32"))] TypableCommand { name: "pipe", aliases: &[], @@ -2973,6 +3020,7 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[ fun: pipe, signature: CommandSignature::none(), }, + #[cfg(not(target_arch = "wasm32"))] TypableCommand { name: "pipe-to", aliases: &[], @@ -2980,6 +3028,7 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[ fun: pipe_to, signature: CommandSignature::none(), }, + #[cfg(not(target_arch = "wasm32"))] TypableCommand { name: "run-shell-command", aliases: &["sh"], @@ -2987,6 +3036,7 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[ fun: run_shell_command, signature: CommandSignature::all(completers::filename) }, + #[cfg(feature = "vcs")] TypableCommand { name: "reset-diff-change", aliases: &["diffget", "diffg"], diff --git a/helix-term/src/compositor.rs b/helix-term/src/compositor.rs index 3dcb5f2bfcbc..da95443e898b 100644 --- a/helix-term/src/compositor.rs +++ b/helix-term/src/compositor.rs @@ -31,8 +31,19 @@ impl<'a> Context<'a> { /// Waits on all pending jobs, and then tries to flush all pending write /// operations for all documents. pub fn block_try_flush_writes(&mut self) -> anyhow::Result<()> { - tokio::task::block_in_place(|| helix_lsp::block_on(self.jobs.finish(self.editor, None)))?; - tokio::task::block_in_place(|| helix_lsp::block_on(self.editor.flush_writes()))?; + #[cfg(not(target_arch = "wasm32"))] + { + tokio::task::block_in_place(|| { + futures_executor::block_on(self.jobs.finish(self.editor, None)) + })?; + tokio::task::block_in_place(|| futures_executor::block_on(self.editor.flush_writes()))?; + } + #[cfg(target_arch = "wasm32")] + { + // TODO(wasm32) this blocks UI? to be tested + futures_executor::block_on(self.jobs.finish(self.editor, None))?; + futures_executor::block_on(self.editor.flush_writes())?; + } Ok(()) } } diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs index bcba8d8e1d45..cc339f1e80bd 100644 --- a/helix-term/src/config.rs +++ b/helix-term/src/config.rs @@ -118,10 +118,14 @@ impl Config { } pub fn load_default() -> Result { + #[cfg(not(target_arch = "wasm32"))] + let read_content = fs::read_to_string; + #[cfg(target_arch = "wasm32")] + let read_content = helix_core::storage::read_to_string; let global_config = - fs::read_to_string(helix_loader::config_file()).map_err(ConfigLoadError::Error); - let local_config = fs::read_to_string(helix_loader::workspace_config_file()) - .map_err(ConfigLoadError::Error); + read_content(helix_loader::config_file()).map_err(ConfigLoadError::Error); + let local_config = + read_content(helix_loader::workspace_config_file()).map_err(ConfigLoadError::Error); Config::load(global_config, local_config) } } diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 598be55b5ff4..28005e03a64e 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -387,7 +387,7 @@ mod tests { fn duplicate_keys_should_panic() { keymap!({ "Normal mode" "i" => normal_mode, - "i" => goto_definition, + "i" => goto_file, }); } diff --git a/helix-term/src/keymap/default.rs b/helix-term/src/keymap/default.rs index 763ed4ae71ce..1fa44c4d766d 100644 --- a/helix-term/src/keymap/default.rs +++ b/helix-term/src/keymap/default.rs @@ -5,7 +5,7 @@ use super::{KeyTrie, Mode}; use helix_core::hashmap; pub fn default() -> HashMap { - let normal = keymap!({ "Normal mode" + let mut normal = keymap!({ "Normal mode" "h" | "left" => move_char_left, "j" | "down" => move_visual_line_down, "k" | "up" => move_visual_line_up, @@ -39,15 +39,9 @@ pub fn default() -> HashMap { "g" => { "Goto" "g" => goto_file_start, "e" => goto_last_line, - "f" => goto_file, "h" => goto_line_start, "l" => goto_line_end, "s" => goto_first_nonwhitespace, - "d" => goto_definition, - "D" => goto_declaration, - "y" => goto_type_definition, - "r" => goto_reference, - "i" => goto_implementation, "t" => goto_window_top, "c" => goto_window_center, "b" => goto_window_bottom, @@ -105,10 +99,8 @@ pub fn default() -> HashMap { "i" => select_textobject_inner, }, "[" => { "Left bracket" - "d" => goto_prev_diag, - "D" => goto_first_diag, - "g" => goto_prev_change, - "G" => goto_first_change, + // "d" => goto_prev_diag, + // "D" => goto_first_diag, "f" => goto_prev_function, "t" => goto_prev_class, "a" => goto_prev_parameter, @@ -118,10 +110,8 @@ pub fn default() -> HashMap { "space" => add_newline_above, }, "]" => { "Right bracket" - "d" => goto_next_diag, - "D" => goto_last_diag, - "g" => goto_next_change, - "G" => goto_last_change, + // "d" => goto_next_diag, + // "D" => goto_last_diag, "f" => goto_next_function, "t" => goto_next_class, "a" => goto_next_parameter, @@ -153,7 +143,6 @@ pub fn default() -> HashMap { ">" => indent, "<" => unindent, - "=" => format_selections, "J" => join_selections, "A-J" => join_selections_space, "K" => keep_selections, @@ -186,8 +175,6 @@ pub fn default() -> HashMap { "C-s" | "s" => hsplit, "C-v" | "v" => vsplit, "C-t" | "t" => transpose_view, - "f" => goto_file_hsplit, - "F" => goto_file_vsplit, "C-q" | "q" => wclose, "C-o" | "o" => wonly, "C-h" | "h" | "left" => jump_view_left, @@ -214,44 +201,11 @@ pub fn default() -> HashMap { "C-s" => save_selection, "space" => { "Space" - "f" => file_picker, - "F" => file_picker_in_current_directory, - "b" => buffer_picker, - "j" => jumplist_picker, - "s" => symbol_picker, - "S" => workspace_symbol_picker, - "d" => diagnostics_picker, - "D" => workspace_diagnostics_picker, - "a" => code_action, - "'" => last_picker, - "g" => { "Debug (experimental)" sticky=true - "l" => dap_launch, - "r" => dap_restart, - "b" => dap_toggle_breakpoint, - "c" => dap_continue, - "h" => dap_pause, - "i" => dap_step_in, - "o" => dap_step_out, - "n" => dap_next, - "v" => dap_variables, - "t" => dap_terminate, - "C-c" => dap_edit_condition, - "C-l" => dap_edit_log, - "s" => { "Switch" - "t" => dap_switch_thread, - "f" => dap_switch_stack_frame, - // sl, sb - }, - "e" => dap_enable_exceptions, - "E" => dap_disable_exceptions, - }, "w" => { "Window" "C-w" | "w" => rotate_view, "C-s" | "s" => hsplit, "C-v" | "v" => vsplit, "C-t" | "t" => transpose_view, - "f" => goto_file_hsplit, - "F" => goto_file_vsplit, "C-q" | "q" => wclose, "C-o" | "o" => wonly, "C-h" | "h" | "left" => jump_view_left, @@ -272,10 +226,6 @@ pub fn default() -> HashMap { "p" => paste_clipboard_after, "P" => paste_clipboard_before, "R" => replace_selections_with_clipboard, - "/" => global_search, - "k" => hover, - "r" => rename_symbol, - "h" => select_references_to_symbol_under_cursor, "?" => command_palette, }, "z" => { "View" @@ -314,16 +264,107 @@ pub fn default() -> HashMap { }, "\"" => select_register, + "C-z" => suspend, + + "C-a" => increment, + "C-x" => decrement, + }); + + // DAP + #[cfg(feature = "dap_lsp")] + normal.merge_nodes(keymap!({ "Normal mode" + "g" => { "Goto" + "d" => goto_definition, + "D" => goto_declaration, + "y" => goto_type_definition, + "r" => goto_reference, + "i" => goto_implementation, + }, + "[" => { "Left bracket" + "d" => goto_prev_diag, + "D" => goto_first_diag, + }, + "]" => { "Right bracket" + "d" => goto_next_diag, + "D" => goto_last_diag, + }, + "=" => format_selections, + + "space" => { "Space" + "s" => symbol_picker, + "S" => workspace_symbol_picker, + "d" => diagnostics_picker, + "D" => workspace_diagnostics_picker, + "a" => code_action, + "g" => { "Debug (experimental)" sticky=true + "l" => dap_launch, + "r" => dap_restart, + "b" => dap_toggle_breakpoint, + "c" => dap_continue, + "h" => dap_pause, + "i" => dap_step_in, + "o" => dap_step_out, + "n" => dap_next, + "v" => dap_variables, + "t" => dap_terminate, + "C-c" => dap_edit_condition, + "C-l" => dap_edit_log, + "s" => { "Switch" + "t" => dap_switch_thread, + "f" => dap_switch_stack_frame, + // sl, sb + }, + "e" => dap_enable_exceptions, + "E" => dap_disable_exceptions, + }, + "k" => hover, + "r" => rename_symbol, + "h" => select_references_to_symbol_under_cursor, + }, + })); + + #[cfg(not(target_arch = "wasm32"))] + normal.merge_nodes(keymap!({ "Normal mode" "|" => shell_pipe, "A-|" => shell_pipe_to, "!" => shell_insert_output, "A-!" => shell_append_output, "$" => shell_keep_pipe, - "C-z" => suspend, + "g" => { "Goto" + "f" => goto_file, + }, + + "C-w" => { "Window" + "f" => goto_file_hsplit, + "F" => goto_file_vsplit, + }, + + "space" => { "Space" + "b" => buffer_picker, + "j" => jumplist_picker, + "'" => last_picker, + "w" => { "Window" + "f" => goto_file_hsplit, + "F" => goto_file_vsplit, + }, + "f" => file_picker, + "F" => file_picker_in_current_directory, + "/" => global_search, + }, + })); + + #[cfg(feature = "vcs")] + normal.merge_nodes(keymap!({ "Normal mode" + "]" => { "Right bracket" + "g" => goto_next_change, + "G" => goto_last_change, + }, + "[" => { "Left bracket" + "g" => goto_prev_change, + "G" => goto_first_change, + }, + })); - "C-a" => increment, - "C-x" => decrement, - }); let mut select = normal.clone(); select.merge_nodes(keymap!({ "Select mode" "h" | "left" => extend_char_left, @@ -363,7 +404,7 @@ pub fn default() -> HashMap { "esc" => normal_mode, "C-s" => commit_undo_checkpoint, - "C-x" => completion, + // "C-x" => completion, "C-r" => insert_register, "C-w" | "A-backspace" => delete_word_backward, diff --git a/helix-term/src/lib.rs b/helix-term/src/lib.rs index 2f6ec12b13fd..a7eccbfbafaf 100644 --- a/helix-term/src/lib.rs +++ b/helix-term/src/lib.rs @@ -6,6 +6,7 @@ pub mod args; pub mod commands; pub mod compositor; pub mod config; +#[cfg(not(target_arch = "wasm32"))] pub mod health; pub mod job; pub mod keymap; @@ -15,7 +16,7 @@ use std::path::Path; use ignore::DirEntry; pub use keymap::macros::*; -#[cfg(not(windows))] +#[cfg(not(any(windows, target_arch = "wasm32")))] fn true_color() -> bool { std::env::var("COLORTERM") .map(|v| matches!(v.as_str(), "truecolor" | "24bit")) @@ -25,6 +26,10 @@ fn true_color() -> bool { fn true_color() -> bool { true } +#[cfg(target_arch = "wasm32")] +fn true_color() -> bool { + true +} /// Function used for filtering dir entries in the various file pickers. fn filter_picker_entry(entry: &DirEntry, root: &Path, dedup_symlinks: bool) -> bool { diff --git a/helix-term/src/main.rs b/helix-term/src/main.rs index 6411a7c5f00c..a61d11ffdd3f 100644 --- a/helix-term/src/main.rs +++ b/helix-term/src/main.rs @@ -105,7 +105,7 @@ FLAGS: } if args.fetch_grammars { - helix_loader::grammar::fetch_grammars()?; + helix_loader::grammar::fetch_grammars(None)?; return Ok(0); } diff --git a/helix-term/src/ui/completion.rs b/helix-term/src/ui/completion.rs index e06147224465..8d2e10a9dfb4 100644 --- a/helix-term/src/ui/completion.rs +++ b/helix-term/src/ui/completion.rs @@ -346,7 +346,7 @@ impl Completion { completion_item: lsp::CompletionItem, ) -> Option { let future = language_server.resolve_completion_item(completion_item)?; - let response = helix_lsp::block_on(future); + let response = futures_executor::block_on(future); match response { Ok(value) => serde_json::from_value(value).ok(), Err(err) => { diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 31195a4e557a..be594ec67ed7 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -1,3 +1,5 @@ +#[cfg(feature = "dap_lsp")] +use crate::ui::Completion; use crate::{ commands::{self, OnKeyCallback}, compositor::{Component, Context, Event, EventResult}, @@ -6,7 +8,7 @@ use crate::{ keymap::{KeymapResult, Keymaps}, ui::{ document::{render_document, LinePos, TextRenderer, TranslatedPosition}, - Completion, ProgressSpinners, + ProgressSpinners, }, }; @@ -33,7 +35,9 @@ use std::{mem::take, num::NonZeroUsize, path::PathBuf, rc::Rc, sync::Arc}; use tui::{buffer::Buffer as Surface, text::Span}; -use super::{completion::CompletionItem, statusline}; +#[cfg(feature = "dap_lsp")] +use super::completion::CompletionItem; +use super::statusline; use super::{document::LineDecoration, lsp::SignatureHelp}; pub struct EditorView { @@ -41,6 +45,7 @@ pub struct EditorView { on_next_key: Option, pseudo_pending: Vec, pub(crate) last_insert: (commands::MappableCommand, Vec), + #[cfg(feature = "dap_lsp")] pub(crate) completion: Option, spinners: ProgressSpinners, /// Tracks if the terminal window is focused by reaction to terminal focus events @@ -71,6 +76,7 @@ impl EditorView { on_next_key: None, pseudo_pending: Vec::new(), last_insert: (commands::MappableCommand::normal_mode, Vec::new()), + #[cfg(feature = "dap_lsp")] completion: None, spinners: ProgressSpinners::default(), terminal_focused: true, @@ -107,6 +113,7 @@ impl EditorView { Self::highlight_cursorcolumn(doc, view, surface, theme, inner, &text_annotations); } + #[cfg(feature = "dap_lsp")] // Set DAP highlights, if needed. if let Some(frame) = editor.current_stack_frame() { let dap_line = frame.line.saturating_sub(1); @@ -365,6 +372,7 @@ impl EditorView { let mut warning_vec = Vec::new(); let mut error_vec = Vec::new(); + #[cfg(feature = "dap_lsp")] for diagnostic in doc.shown_diagnostics() { // Separate diagnostics into different Vecs by severity. let (vec, scope) = match diagnostic.severity { @@ -658,6 +666,7 @@ impl EditorView { .primary() .cursor(doc.text().slice(..)); + #[cfg(feature = "dap_lsp")] let diagnostics = doc.shown_diagnostics().filter(|diagnostic| { diagnostic.range.start <= cursor && diagnostic.range.end >= cursor }); @@ -669,6 +678,7 @@ impl EditorView { let mut lines = Vec::new(); let background_style = theme.get("ui.background"); + #[cfg(feature = "dap_lsp")] for diagnostic in diagnostics { let style = Style::reset() .patch(background_style) @@ -820,9 +830,11 @@ impl EditorView { self.last_insert.0 = command.clone(); self.last_insert.1.clear(); + #[cfg(feature = "dap_lsp")] commands::signature_help_impl(cxt, commands::SignatureHelpInvoked::Automatic); } (Mode::Insert, Mode::Normal) => { + #[cfg(feature = "dap_lsp")] // if exiting insert mode, remove completion self.clear_completion(cxt.editor); cxt.editor.completion_request_handle = None; @@ -959,6 +971,7 @@ impl EditorView { } } + #[cfg(feature = "dap_lsp")] #[allow(clippy::too_many_arguments)] pub fn set_completion( &mut self, @@ -987,6 +1000,7 @@ impl EditorView { Some(area) } + #[cfg(feature = "dap_lsp")] pub fn clear_completion(&mut self, editor: &mut Editor) { self.completion = None; if let Some(last_completion) = editor.last_completion.take() { @@ -1010,8 +1024,10 @@ impl EditorView { } pub fn handle_idle_timeout(&mut self, cx: &mut commands::Context) -> EventResult { + #[cfg(feature = "dap_lsp")] commands::compute_inlay_hints_for_all_views(cx.editor, cx.jobs); + #[cfg(feature = "dap_lsp")] if let Some(completion) = &mut self.completion { return if completion.ensure_item_resolved(cx) { EventResult::Consumed(None) @@ -1024,6 +1040,7 @@ impl EditorView { return EventResult::Ignored(None); } + #[cfg(feature = "dap_lsp")] crate::commands::insert::idle_completion(cx); EventResult::Consumed(None) @@ -1083,6 +1100,7 @@ impl EditorView { doc.set_selection(view_id, Selection::point(pos)); } + #[cfg(feature = "dap_lsp")] if view_id != prev_view_id { self.clear_completion(editor); } @@ -1103,6 +1121,7 @@ impl EditorView { None => return EventResult::Ignored(None), }; + #[cfg(feature = "dap_lsp")] if let Some(char_idx) = view.pos_at_visual_coords(doc, coords.row as u16, coords.col as u16, true) { @@ -1186,6 +1205,7 @@ impl EditorView { view.pos_at_visual_coords(doc, coords.row as u16, coords.col as u16, true) { doc.set_selection(view_id, Selection::point(pos)); + #[cfg(feature = "dap_lsp")] if modifiers == KeyModifiers::ALT { commands::MappableCommand::dap_edit_log.execute(cxt); } else { @@ -1287,6 +1307,7 @@ impl Component for EditorView { Mode::Insert => { // let completion swallow the event if necessary let mut consumed = false; + #[cfg(feature = "dap_lsp")] if let Some(completion) = &mut self.completion { let res = { // use a fake context here @@ -1331,6 +1352,7 @@ impl Component for EditorView { // record last_insert key self.last_insert.1.push(InsertEvent::Key(key)); + #[cfg(feature = "dap_lsp")] // lastly we recalculate completion if let Some(completion) = &mut self.completion { completion.update(&mut cx); @@ -1493,6 +1515,7 @@ impl Component for EditorView { } } + #[cfg(feature = "dap_lsp")] if let Some(completion) = self.completion.as_mut() { completion.render(area, surface, cx); } diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs index 660bbfea363d..7059a9a3639d 100644 --- a/helix-term/src/ui/mod.rs +++ b/helix-term/src/ui/mod.rs @@ -1,3 +1,4 @@ +#[cfg(feature = "dap_lsp")] mod completion; mod document; pub(crate) mod editor; @@ -16,6 +17,7 @@ mod text; use crate::compositor::{Component, Compositor}; use crate::filter_picker_entry; use crate::job::{self, Callback}; +#[cfg(feature = "dap_lsp")] pub use completion::{Completion, CompletionItem}; pub use editor::EditorView; pub use markdown::Markdown; @@ -155,6 +157,7 @@ pub fn regex_prompt( cx.push_layer(Box::new(prompt)); } +#[cfg(not(target_arch = "wasm32"))] pub fn file_picker(root: PathBuf, config: &helix_view::editor::Config) -> Picker { use ignore::{types::TypesBuilder, WalkBuilder}; use std::time::Instant; @@ -214,14 +217,14 @@ pub fn file_picker(root: PathBuf, config: &helix_view::editor::Config) -> Picker }) .with_preview(|_editor, path| Some((path.clone().into(), None))); let injector = picker.injector(); - let timeout = std::time::Instant::now() + std::time::Duration::from_millis(30); + let timeout = Instant::now() + std::time::Duration::from_millis(30); let mut hit_timeout = false; for file in &mut files { if injector.push(file).is_err() { break; } - if std::time::Instant::now() >= timeout { + if Instant::now() >= timeout { hit_timeout = true; break; } @@ -268,10 +271,14 @@ pub mod completers { } pub fn theme(_editor: &Editor, input: &str) -> Vec { + #[cfg(not(target_arch = "wasm32"))] let mut names = theme::Loader::read_names(&helix_loader::config_dir().join("themes")); + #[cfg(not(target_arch = "wasm32"))] for rt_dir in helix_loader::runtime_dirs() { names.extend(theme::Loader::read_names(&rt_dir.join("themes"))); } + #[cfg(target_arch = "wasm32")] + let mut names = theme::themes(); names.push("default".into()); names.push("base16_default".into()); names.sort(); @@ -348,6 +355,7 @@ pub mod completers { .collect() } + #[cfg(feature = "dap_lsp")] pub fn lsp_workspace_command(editor: &Editor, input: &str) -> Vec { let Some(options) = doc!(editor) .language_servers_with_feature(LanguageServerFeature::WorkspaceCommand) diff --git a/helix-term/src/ui/picker.rs b/helix-term/src/ui/picker.rs index 2c41fc12d685..1b2c28c5f041 100644 --- a/helix-term/src/ui/picker.rs +++ b/helix-term/src/ui/picker.rs @@ -204,6 +204,7 @@ pub struct Picker { } impl Picker { + // TODO(wasm32) nucleo works with a threadpool, find an alternative pub fn stream(editor_data: T::Data) -> (Nucleo, Injector) { let matcher = Nucleo::new( Config::DEFAULT, @@ -224,6 +225,7 @@ impl Picker { editor_data: T::Data, callback_fn: impl Fn(&mut Context, &T, Action) + 'static, ) -> Self { + // TODO(wasm32) nucleo works with a threadpool, find an alternative let matcher = Nucleo::new( Config::DEFAULT, Arc::new(helix_event::request_redraw), @@ -480,8 +482,7 @@ impl Picker { .find::>>() .map(|overlay| &mut overlay.content.file_picker), }; - let Some(picker) = picker - else { + let Some(picker) = picker else { log::info!("picker closed before syntax highlighting finished"); return; }; diff --git a/helix-term/src/ui/spinner.rs b/helix-term/src/ui/spinner.rs index 68965469d32e..6e3647fc3fbe 100644 --- a/helix-term/src/ui/spinner.rs +++ b/helix-term/src/ui/spinner.rs @@ -1,4 +1,9 @@ -use std::{collections::HashMap, time::Instant}; +use std::collections::HashMap; + +#[cfg(target_arch = "wasm32")] +use instant::Instant; +#[cfg(not(target_arch = "wasm32"))] +use std::time::Instant; #[derive(Default, Debug)] pub struct ProgressSpinners { diff --git a/helix-term/src/ui/statusline.rs b/helix-term/src/ui/statusline.rs index 52dd49f9e212..ca40589aa7cf 100644 --- a/helix-term/src/ui/statusline.rs +++ b/helix-term/src/ui/statusline.rs @@ -1,4 +1,5 @@ use helix_core::{coords_at_pos, encoding, Position}; +#[cfg(feature = "dap_lsp")] use helix_lsp::lsp::DiagnosticSeverity; use helix_view::document::DEFAULT_LANGUAGE_NAME; use helix_view::{ @@ -139,6 +140,7 @@ where { match element_id { helix_view::editor::StatusLineElement::Mode => render_mode, + #[cfg(feature = "dap_lsp")] helix_view::editor::StatusLineElement::Spinner => render_lsp_spinner, helix_view::editor::StatusLineElement::FileBaseName => render_file_base_name, helix_view::editor::StatusLineElement::FileName => render_file_name, @@ -149,7 +151,9 @@ where helix_view::editor::StatusLineElement::FileEncoding => render_file_encoding, helix_view::editor::StatusLineElement::FileLineEnding => render_file_line_ending, helix_view::editor::StatusLineElement::FileType => render_file_type, + #[cfg(feature = "dap_lsp")] helix_view::editor::StatusLineElement::Diagnostics => render_diagnostics, + #[cfg(feature = "dap_lsp")] helix_view::editor::StatusLineElement::WorkspaceDiagnostics => render_workspace_diagnostics, helix_view::editor::StatusLineElement::Selections => render_selections, helix_view::editor::StatusLineElement::PrimarySelectionLength => { @@ -199,6 +203,7 @@ where ); } +#[cfg(feature = "dap_lsp")] // TODO think about handling multiple language servers fn render_lsp_spinner(context: &mut RenderContext, write: F) where @@ -221,6 +226,7 @@ where ); } +#[cfg(feature = "dap_lsp")] fn render_diagnostics(context: &mut RenderContext, write: F) where F: Fn(&mut RenderContext, String, Option