Piepmatz 3d5f853b03
Start to Add WASM Support Again (#14418)
<!--
if this PR closes one or more issues, you can automatically link the PR
with
them by using one of the [*linking
keywords*](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword),
e.g.
- this PR should close #xxxx
- fixes #xxxx

you can also mention related issues, PRs or discussions!
-->

# Description
<!--
Thank you for improving Nushell. Please, check our [contributing
guide](../CONTRIBUTING.md) and talk to the core team before making major
changes.

Description of your pull request goes here. **Provide examples and/or
screenshots** if your changes affect the user experience.
-->
The [nushell/demo](https://github.com/nushell/demo) project successfully
demonstrated running Nushell in the browser using WASM. However, the
current version of Nushell cannot be easily built for the
`wasm32-unknown-unknown` target, the default for `wasm-bindgen`.

This PR introduces initial support for the `wasm32-unknown-unknown`
target by disabling OS-dependent features such as filesystem access, IO,
and platform/system-specific functionality. This separation is achieved
using a new `os` feature in the following crates:

 - `nu-cmd-lang`
 - `nu-command`
 - `nu-engine`
 - `nu-protocol`

The `os` feature includes all functionality that interacts with an
operating system. It is enabled by default, but can be disabled using
`--no-default-features`. All crates that depend on these core crates now
use `--no-default-features` to allow compilation for WASM.

To demonstrate compatibility, the following script builds all crates
expected to work with WASM. Direct user interaction, running external
commands, working with plugins, and features requiring `openssl` are out
of scope for now due to their complexity or reliance on C libraries,
which are difficult to compile and link in a WASM environment.

```nushell
[ # compatible crates
	"nu-cmd-base",
	"nu-cmd-extra",
	"nu-cmd-lang",
	"nu-color-config",
	"nu-command",
	"nu-derive-value",
	"nu-engine",
	"nu-glob",
	"nu-json",
	"nu-parser",
	"nu-path",
	"nu-pretty-hex",
	"nu-protocol",
	"nu-std",
	"nu-system",
	"nu-table",
	"nu-term-grid",
	"nu-utils",
	"nuon"
] | each {cargo build -p $in --target wasm32-unknown-unknown --no-default-features}
```

## Caveats
This PR has a few caveats:
1. **`miette` and `terminal-size` Dependency Issue**
`miette` depends on `terminal-size`, which uses `rustix` when the target
is not Windows. However, `rustix` requires `std::os::unix`, which is
unavailable in WASM. To address this, I opened a
[PR](https://github.com/eminence/terminal-size/pull/68) for
`terminal-size` to conditionally compile `rustix` only when the target
is Unix. For now, the `Cargo.toml` includes patches to:
    - Use my forked version of `terminal-size`.
- ~~Use an unreleased version of `miette` that depends on
`terminal-size@0.4`.~~

These patches are temporary and can be removed once the upstream changes
are merged and released.

2. **Test Output Adjustments**
Due to the slight bump in the `miette` version, one test required
adjustments to accommodate minor formatting changes in the error output,
such as shifted newlines.

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->
This shouldn't break anything but allows using some crates for targeting
`wasm32-unknown-unknown` to revive the demo page eventually.

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

Make sure you've run and fixed any issues with these commands:

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
-->

- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`

I did not add any extra tests, I just checked that compiling works, also
when using the host target but unselecting the `os` feature.

# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->
~~Breaking the wasm support can be easily done by adding some `use`s or
by adding a new dependency, we should definitely add some CI that also
at least builds against wasm to make sure that building for it keep
working.~~
I added a job to build wasm.

---------

Co-authored-by: Ian Manske <ian.manske@pm.me>
2024-11-30 07:57:11 -06:00

491 lines
17 KiB
Rust

#[cfg(windows)]
use crossterm_winapi::{ConsoleMode, Handle};
use lscolors::LsColors;
use std::io::{self, Result, Write};
pub fn enable_vt_processing() -> Result<()> {
#[cfg(windows)]
{
let console_out_mode = ConsoleMode::from(Handle::current_out_handle()?);
let old_out_mode = console_out_mode.mode()?;
let console_in_mode = ConsoleMode::from(Handle::current_in_handle()?);
let old_in_mode = console_in_mode.mode()?;
enable_vt_processing_input(console_in_mode, old_in_mode)?;
enable_vt_processing_output(console_out_mode, old_out_mode)?;
}
Ok(())
}
#[cfg(windows)]
fn enable_vt_processing_input(console_in_mode: ConsoleMode, mode: u32) -> Result<()> {
//
// Input Mode flags:
//
// #define ENABLE_PROCESSED_INPUT 0x0001
// #define ENABLE_LINE_INPUT 0x0002
// #define ENABLE_ECHO_INPUT 0x0004
// #define ENABLE_WINDOW_INPUT 0x0008
// #define ENABLE_MOUSE_INPUT 0x0010
// #define ENABLE_INSERT_MODE 0x0020
// #define ENABLE_QUICK_EDIT_MODE 0x0040
// #define ENABLE_EXTENDED_FLAGS 0x0080
// #define ENABLE_AUTO_POSITION 0x0100
// #define ENABLE_VIRTUAL_TERMINAL_INPUT 0x0200
const ENABLE_PROCESSED_INPUT: u32 = 0x0001;
const ENABLE_LINE_INPUT: u32 = 0x0002;
const ENABLE_ECHO_INPUT: u32 = 0x0004;
const ENABLE_VIRTUAL_TERMINAL_INPUT: u32 = 0x0200;
console_in_mode.set_mode(
mode | ENABLE_VIRTUAL_TERMINAL_INPUT
& ENABLE_ECHO_INPUT
& ENABLE_LINE_INPUT
& ENABLE_PROCESSED_INPUT,
)
}
#[cfg(windows)]
fn enable_vt_processing_output(console_out_mode: ConsoleMode, mode: u32) -> Result<()> {
//
// Output Mode flags:
//
// #define ENABLE_PROCESSED_OUTPUT 0x0001
// #define ENABLE_WRAP_AT_EOL_OUTPUT 0x0002
// #define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
// #define DISABLE_NEWLINE_AUTO_RETURN 0x0008
// #define ENABLE_LVB_GRID_WORLDWIDE 0x0010
pub const ENABLE_PROCESSED_OUTPUT: u32 = 0x0001;
pub const ENABLE_VIRTUAL_TERMINAL_PROCESSING: u32 = 0x0004;
console_out_mode.set_mode(mode | ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING)
}
pub fn stdout_write_all_and_flush<T>(output: T) -> Result<()>
where
T: AsRef<[u8]>,
{
let stdout = std::io::stdout();
stdout.lock().write_all(output.as_ref())?;
stdout.lock().flush()
}
pub fn stderr_write_all_and_flush<T>(output: T) -> Result<()>
where
T: AsRef<[u8]>,
{
let stderr = std::io::stderr();
let ret = match stderr.lock().write_all(output.as_ref()) {
Ok(_) => Ok(stderr.lock().flush()?),
Err(err) => Err(err),
};
ret
}
// See default_files/README.md for a description of these files
pub fn get_default_env() -> &'static str {
include_str!("default_files/default_env.nu")
}
pub fn get_scaffold_env() -> &'static str {
include_str!("default_files/scaffold_env.nu")
}
pub fn get_sample_env() -> &'static str {
include_str!("default_files/sample_env.nu")
}
pub fn get_default_config() -> &'static str {
include_str!("default_files/default_config.nu")
}
pub fn get_scaffold_config() -> &'static str {
include_str!("default_files/scaffold_config.nu")
}
pub fn get_sample_config() -> &'static str {
include_str!("default_files/sample_config.nu")
}
pub fn get_ls_colors(lscolors_env_string: Option<String>) -> LsColors {
if let Some(s) = lscolors_env_string {
LsColors::from_string(&s)
} else {
LsColors::from_string(
&[
"st=0",
"di=0;38;5;81",
"so=0;38;5;16;48;5;203",
"ln=0;38;5;203",
"cd=0;38;5;203;48;5;236",
"ex=1;38;5;203",
"or=0;38;5;16;48;5;203",
"fi=0",
"bd=0;38;5;81;48;5;236",
"ow=0",
"mi=0;38;5;16;48;5;203",
"*~=0;38;5;243",
"no=0",
"tw=0",
"pi=0;38;5;16;48;5;81",
"*.z=4;38;5;203",
"*.t=0;38;5;48",
"*.o=0;38;5;243",
"*.d=0;38;5;48",
"*.a=1;38;5;203",
"*.c=0;38;5;48",
"*.m=0;38;5;48",
"*.p=0;38;5;48",
"*.r=0;38;5;48",
"*.h=0;38;5;48",
"*.ml=0;38;5;48",
"*.ll=0;38;5;48",
"*.gv=0;38;5;48",
"*.cp=0;38;5;48",
"*.xz=4;38;5;203",
"*.hs=0;38;5;48",
"*css=0;38;5;48",
"*.ui=0;38;5;149",
"*.pl=0;38;5;48",
"*.ts=0;38;5;48",
"*.gz=4;38;5;203",
"*.so=1;38;5;203",
"*.cr=0;38;5;48",
"*.fs=0;38;5;48",
"*.bz=4;38;5;203",
"*.ko=1;38;5;203",
"*.as=0;38;5;48",
"*.sh=0;38;5;48",
"*.pp=0;38;5;48",
"*.el=0;38;5;48",
"*.py=0;38;5;48",
"*.lo=0;38;5;243",
"*.bc=0;38;5;243",
"*.cc=0;38;5;48",
"*.pm=0;38;5;48",
"*.rs=0;38;5;48",
"*.di=0;38;5;48",
"*.jl=0;38;5;48",
"*.rb=0;38;5;48",
"*.md=0;38;5;185",
"*.js=0;38;5;48",
"*.cjs=0;38;5;48",
"*.mjs=0;38;5;48",
"*.go=0;38;5;48",
"*.vb=0;38;5;48",
"*.hi=0;38;5;243",
"*.kt=0;38;5;48",
"*.hh=0;38;5;48",
"*.cs=0;38;5;48",
"*.mn=0;38;5;48",
"*.nb=0;38;5;48",
"*.7z=4;38;5;203",
"*.ex=0;38;5;48",
"*.rm=0;38;5;208",
"*.ps=0;38;5;186",
"*.td=0;38;5;48",
"*.la=0;38;5;243",
"*.aux=0;38;5;243",
"*.xmp=0;38;5;149",
"*.mp4=0;38;5;208",
"*.rpm=4;38;5;203",
"*.m4a=0;38;5;208",
"*.zip=4;38;5;203",
"*.dll=1;38;5;203",
"*.bcf=0;38;5;243",
"*.awk=0;38;5;48",
"*.aif=0;38;5;208",
"*.zst=4;38;5;203",
"*.bak=0;38;5;243",
"*.tgz=4;38;5;203",
"*.com=1;38;5;203",
"*.clj=0;38;5;48",
"*.sxw=0;38;5;186",
"*.vob=0;38;5;208",
"*.fsx=0;38;5;48",
"*.doc=0;38;5;186",
"*.mkv=0;38;5;208",
"*.tbz=4;38;5;203",
"*.ogg=0;38;5;208",
"*.wma=0;38;5;208",
"*.mid=0;38;5;208",
"*.kex=0;38;5;186",
"*.out=0;38;5;243",
"*.ltx=0;38;5;48",
"*.sql=0;38;5;48",
"*.ppt=0;38;5;186",
"*.tex=0;38;5;48",
"*.odp=0;38;5;186",
"*.log=0;38;5;243",
"*.arj=4;38;5;203",
"*.ipp=0;38;5;48",
"*.sbt=0;38;5;48",
"*.jpg=0;38;5;208",
"*.yml=0;38;5;149",
"*.txt=0;38;5;185",
"*.csv=0;38;5;185",
"*.dox=0;38;5;149",
"*.pro=0;38;5;149",
"*.bst=0;38;5;149",
"*TODO=1",
"*.mir=0;38;5;48",
"*.bat=1;38;5;203",
"*.m4v=0;38;5;208",
"*.pod=0;38;5;48",
"*.cfg=0;38;5;149",
"*.pas=0;38;5;48",
"*.tml=0;38;5;149",
"*.bib=0;38;5;149",
"*.ini=0;38;5;149",
"*.apk=4;38;5;203",
"*.h++=0;38;5;48",
"*.pyc=0;38;5;243",
"*.img=4;38;5;203",
"*.rst=0;38;5;185",
"*.swf=0;38;5;208",
"*.htm=0;38;5;185",
"*.ttf=0;38;5;208",
"*.elm=0;38;5;48",
"*hgrc=0;38;5;149",
"*.bmp=0;38;5;208",
"*.fsi=0;38;5;48",
"*.pgm=0;38;5;208",
"*.dpr=0;38;5;48",
"*.xls=0;38;5;186",
"*.tcl=0;38;5;48",
"*.mli=0;38;5;48",
"*.ppm=0;38;5;208",
"*.bbl=0;38;5;243",
"*.lua=0;38;5;48",
"*.asa=0;38;5;48",
"*.pbm=0;38;5;208",
"*.avi=0;38;5;208",
"*.def=0;38;5;48",
"*.mov=0;38;5;208",
"*.hxx=0;38;5;48",
"*.tif=0;38;5;208",
"*.fon=0;38;5;208",
"*.zsh=0;38;5;48",
"*.png=0;38;5;208",
"*.inc=0;38;5;48",
"*.jar=4;38;5;203",
"*.swp=0;38;5;243",
"*.pid=0;38;5;243",
"*.gif=0;38;5;208",
"*.ind=0;38;5;243",
"*.erl=0;38;5;48",
"*.ilg=0;38;5;243",
"*.eps=0;38;5;208",
"*.tsx=0;38;5;48",
"*.git=0;38;5;243",
"*.inl=0;38;5;48",
"*.rtf=0;38;5;186",
"*.hpp=0;38;5;48",
"*.kts=0;38;5;48",
"*.deb=4;38;5;203",
"*.svg=0;38;5;208",
"*.pps=0;38;5;186",
"*.ps1=0;38;5;48",
"*.c++=0;38;5;48",
"*.cpp=0;38;5;48",
"*.bsh=0;38;5;48",
"*.php=0;38;5;48",
"*.exs=0;38;5;48",
"*.toc=0;38;5;243",
"*.mp3=0;38;5;208",
"*.epp=0;38;5;48",
"*.rar=4;38;5;203",
"*.wav=0;38;5;208",
"*.xlr=0;38;5;186",
"*.tmp=0;38;5;243",
"*.cxx=0;38;5;48",
"*.iso=4;38;5;203",
"*.dmg=4;38;5;203",
"*.gvy=0;38;5;48",
"*.bin=4;38;5;203",
"*.wmv=0;38;5;208",
"*.blg=0;38;5;243",
"*.ods=0;38;5;186",
"*.psd=0;38;5;208",
"*.mpg=0;38;5;208",
"*.dot=0;38;5;48",
"*.cgi=0;38;5;48",
"*.xml=0;38;5;185",
"*.htc=0;38;5;48",
"*.ics=0;38;5;186",
"*.bz2=4;38;5;203",
"*.tar=4;38;5;203",
"*.csx=0;38;5;48",
"*.ico=0;38;5;208",
"*.sxi=0;38;5;186",
"*.nix=0;38;5;149",
"*.pkg=4;38;5;203",
"*.bag=4;38;5;203",
"*.fnt=0;38;5;208",
"*.idx=0;38;5;243",
"*.xcf=0;38;5;208",
"*.exe=1;38;5;203",
"*.flv=0;38;5;208",
"*.fls=0;38;5;243",
"*.otf=0;38;5;208",
"*.vcd=4;38;5;203",
"*.vim=0;38;5;48",
"*.sty=0;38;5;243",
"*.pdf=0;38;5;186",
"*.odt=0;38;5;186",
"*.purs=0;38;5;48",
"*.h264=0;38;5;208",
"*.jpeg=0;38;5;208",
"*.dart=0;38;5;48",
"*.pptx=0;38;5;186",
"*.lock=0;38;5;243",
"*.bash=0;38;5;48",
"*.rlib=0;38;5;243",
"*.hgrc=0;38;5;149",
"*.psm1=0;38;5;48",
"*.toml=0;38;5;149",
"*.tbz2=4;38;5;203",
"*.yaml=0;38;5;149",
"*.make=0;38;5;149",
"*.orig=0;38;5;243",
"*.html=0;38;5;185",
"*.fish=0;38;5;48",
"*.diff=0;38;5;48",
"*.xlsx=0;38;5;186",
"*.docx=0;38;5;186",
"*.json=0;38;5;149",
"*.psd1=0;38;5;48",
"*.tiff=0;38;5;208",
"*.flac=0;38;5;208",
"*.java=0;38;5;48",
"*.less=0;38;5;48",
"*.mpeg=0;38;5;208",
"*.conf=0;38;5;149",
"*.lisp=0;38;5;48",
"*.epub=0;38;5;186",
"*.cabal=0;38;5;48",
"*.patch=0;38;5;48",
"*.shtml=0;38;5;185",
"*.class=0;38;5;243",
"*.xhtml=0;38;5;185",
"*.mdown=0;38;5;185",
"*.dyn_o=0;38;5;243",
"*.cache=0;38;5;243",
"*.swift=0;38;5;48",
"*README=0;38;5;16;48;5;186",
"*passwd=0;38;5;149",
"*.ipynb=0;38;5;48",
"*shadow=0;38;5;149",
"*.toast=4;38;5;203",
"*.cmake=0;38;5;149",
"*.scala=0;38;5;48",
"*.dyn_hi=0;38;5;243",
"*.matlab=0;38;5;48",
"*.config=0;38;5;149",
"*.gradle=0;38;5;48",
"*.groovy=0;38;5;48",
"*.ignore=0;38;5;149",
"*LICENSE=0;38;5;249",
"*TODO.md=1",
"*COPYING=0;38;5;249",
"*.flake8=0;38;5;149",
"*INSTALL=0;38;5;16;48;5;186",
"*setup.py=0;38;5;149",
"*.gemspec=0;38;5;149",
"*.desktop=0;38;5;149",
"*Makefile=0;38;5;149",
"*Doxyfile=0;38;5;149",
"*TODO.txt=1",
"*README.md=0;38;5;16;48;5;186",
"*.kdevelop=0;38;5;149",
"*.rgignore=0;38;5;149",
"*configure=0;38;5;149",
"*.DS_Store=0;38;5;243",
"*.fdignore=0;38;5;149",
"*COPYRIGHT=0;38;5;249",
"*.markdown=0;38;5;185",
"*.cmake.in=0;38;5;149",
"*.gitconfig=0;38;5;149",
"*INSTALL.md=0;38;5;16;48;5;186",
"*CODEOWNERS=0;38;5;149",
"*.gitignore=0;38;5;149",
"*Dockerfile=0;38;5;149",
"*SConstruct=0;38;5;149",
"*.scons_opt=0;38;5;243",
"*README.txt=0;38;5;16;48;5;186",
"*SConscript=0;38;5;149",
"*.localized=0;38;5;243",
"*.travis.yml=0;38;5;186",
"*Makefile.in=0;38;5;243",
"*.gitmodules=0;38;5;149",
"*LICENSE-MIT=0;38;5;249",
"*Makefile.am=0;38;5;149",
"*INSTALL.txt=0;38;5;16;48;5;186",
"*MANIFEST.in=0;38;5;149",
"*.synctex.gz=0;38;5;243",
"*.fdb_latexmk=0;38;5;243",
"*CONTRIBUTORS=0;38;5;16;48;5;186",
"*configure.ac=0;38;5;149",
"*.applescript=0;38;5;48",
"*appveyor.yml=0;38;5;186",
"*.clang-format=0;38;5;149",
"*.gitattributes=0;38;5;149",
"*LICENSE-APACHE=0;38;5;249",
"*CMakeCache.txt=0;38;5;243",
"*CMakeLists.txt=0;38;5;149",
"*CONTRIBUTORS.md=0;38;5;16;48;5;186",
"*requirements.txt=0;38;5;149",
"*CONTRIBUTORS.txt=0;38;5;16;48;5;186",
"*.sconsign.dblite=0;38;5;243",
"*package-lock.json=0;38;5;243",
"*.CFUserTextEncoding=0;38;5;243",
"*.fb2=0;38;5;186",
]
.join(":"),
)
}
}
// Log some performance metrics (green text with yellow timings)
#[macro_export]
macro_rules! perf {
($msg:expr, $dur:expr, $use_color:expr) => {
if $use_color {
log::info!(
"perf: {}:{}:{} \x1b[32m{}\x1b[0m took \x1b[33m{:?}\x1b[0m",
file!(),
line!(),
column!(),
$msg,
$dur.elapsed(),
);
} else {
log::info!(
"perf: {}:{}:{} {} took {:?}",
file!(),
line!(),
column!(),
$msg,
$dur.elapsed(),
);
}
};
}
/// Returns the terminal size (columns, rows).
///
/// This utility variant allows getting a fallback value when compiling for wasm32 without having
/// to rearrange other bits of the codebase.
///
/// See [`crossterm::terminal::size`].
pub fn terminal_size() -> io::Result<(u16, u16)> {
#[cfg(feature = "os")]
return crossterm::terminal::size();
#[cfg(not(feature = "os"))]
return Err(io::Error::from(io::ErrorKind::Unsupported));
}