2022-01-18 20:32:45 +01:00
|
|
|
use crate::{
|
2022-05-05 17:10:03 +02:00
|
|
|
completions::NuCompleter,
|
|
|
|
prompt_update,
|
|
|
|
reedline_config::{add_menus, create_keybindings, KeybindingsMode},
|
2023-03-20 05:05:22 +01:00
|
|
|
util::eval_source,
|
2022-05-05 17:10:03 +02:00
|
|
|
NuHighlighter, NuValidator, NushellPrompt,
|
2022-01-18 20:32:45 +01:00
|
|
|
};
|
2023-04-14 22:14:57 +02:00
|
|
|
use crossterm::cursor::SetCursorStyle;
|
2023-01-24 21:28:59 +01:00
|
|
|
use log::{trace, warn};
|
2023-07-07 03:16:17 +02:00
|
|
|
use miette::{ErrReport, IntoDiagnostic, Result};
|
2023-06-23 21:23:08 +02:00
|
|
|
use nu_cmd_base::util::get_guaranteed_cwd;
|
2023-09-29 16:36:03 +02:00
|
|
|
use nu_cmd_base::{hook::eval_hook, util::get_editor};
|
color_config now accepts closures as color values (#7141)
# Description
Closes #6909. You can now add closures to your `color_config` themes.
Whenever a value would be printed with `table`, the closure is run with
the value piped-in. The closure must return either a {fg,bg,attr} record
or a color name (`'light_red'` etc.). This returned style is used to
colour the value.
This is entirely backwards-compatible with existing config.nu files.
Example code excerpt:
```
let my_theme = {
header: green_bold
bool: { if $in { 'light_cyan' } else { 'light_red' } }
int: purple_bold
filesize: { |e| if $e == 0b { 'gray' } else if $e < 1mb { 'purple_bold' } else { 'cyan_bold' } }
duration: purple_bold
date: { (date now) - $in | if $in > 1wk { 'cyan_bold' } else if $in > 1day { 'green_bold' } else { 'yellow_bold' } }
range: yellow_bold
string: { if $in =~ '^#\w{6}$' { $in } else { 'white' } }
nothing: white
```
Example output with this in effect:
![2022-11-16 12 47 23 AM - style_computer
rs_-_nushell_-_VSCodium](https://user-images.githubusercontent.com/83939/201952558-482de05d-69c7-4bf2-91fc-d0964bf71264.png)
![2022-11-16 12 39 41 AM - style_computer
rs_-_nushell_-_VSCodium](https://user-images.githubusercontent.com/83939/201952580-2384bb86-b680-40fe-8192-71bae396c738.png)
![2022-11-15 09 21 54 PM - run_external
rs_-_nushell_-_VSCodium](https://user-images.githubusercontent.com/83939/201952601-343fc15d-e4a8-4a92-ad89-9a7d17d42748.png)
Slightly important notes:
* Some color_config names, namely "separator", "empty" and "hints", pipe
in `null` instead of a value.
* Currently, doing anything non-trivial inside a closure has an
understandably big perf hit. I currently do not actually recommend
something like `string: { if $in =~ '^#\w{6}$' { $in } else { 'white' }
}` for serious work, mainly because of the abundance of string-type data
in the world. Nevertheless, lesser-used types like "date" and "duration"
work well with this.
* I had to do some reorganisation in order to make it possible to call
`eval_block()` that late in table rendering. I invented a new struct
called "StyleComputer" which holds the engine_state and stack of the
initial `table` command (implicit or explicit).
* StyleComputer has a `compute()` method which takes a color_config name
and a nu value, and always returns the correct Style, so you don't have
to worry about A) the color_config value was set at all, B) whether it
was set to a closure or not, or C) which default style to use in those
cases.
* Currently, errors encountered during execution of the closures are
thrown in the garbage. Any other ideas are welcome. (Nonetheless, errors
result in a huge perf hit when they are encountered. I think what should
be done is to assume something terrible happened to the user's config
and invalidate the StyleComputer for that `table` run, thus causing
subsequent output to just be Style::default().)
* More thorough tests are forthcoming - ran into some difficulty using
`nu!` to take an alternative config, and for some reason `let-env config
=` statements don't seem to work inside `nu!` pipelines(???)
* The default config.nu has not been updated to make use of this yet. Do
tell if you think I should incorporate that into this.
# User-Facing Changes
See above.
# 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 --features=extra -- -D warnings -D
clippy::unwrap_used -A clippy::needless_collect` to check that you're
using the standard code style
- `cargo test --workspace --features=extra` to check that all tests pass
# 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.
2022-12-17 14:07:56 +01:00
|
|
|
use nu_color_config::StyleComputer;
|
2024-01-25 08:31:46 +01:00
|
|
|
use nu_engine::{convert_env_values, env_to_strings};
|
2023-05-30 16:38:45 +02:00
|
|
|
use nu_parser::{lex, parse, trim_quotes_str};
|
2022-01-18 09:48:28 +01:00
|
|
|
use nu_protocol::{
|
2023-01-13 21:37:39 +01:00
|
|
|
config::NuCursorShape,
|
2023-03-16 23:45:35 +01:00
|
|
|
engine::{EngineState, Stack, StateWorkingSet},
|
2023-09-01 08:18:55 +02:00
|
|
|
eval_const::create_nu_constant,
|
2024-01-31 18:32:19 +01:00
|
|
|
report_error_new, HistoryConfig, HistoryFileFormat, PipelineData, ShellError, Span, Spanned,
|
|
|
|
Value, NU_VARIABLE_ID,
|
2022-01-18 09:48:28 +01:00
|
|
|
};
|
2023-01-24 21:28:59 +01:00
|
|
|
use nu_utils::utils::perf;
|
2023-07-07 03:16:17 +02:00
|
|
|
use reedline::{
|
2023-10-20 11:21:58 +02:00
|
|
|
CursorConfig, CwdAwareHinter, EditCommand, Emacs, FileBackedHistory, HistorySessionId,
|
|
|
|
Reedline, SqliteBackedHistory, Vi,
|
2023-07-07 03:16:17 +02:00
|
|
|
};
|
2022-09-19 16:28:36 +02:00
|
|
|
use std::{
|
2023-09-29 16:36:03 +02:00
|
|
|
env::temp_dir,
|
2023-08-25 10:54:44 +02:00
|
|
|
io::{self, IsTerminal, Write},
|
2024-02-22 19:14:10 +01:00
|
|
|
panic::{catch_unwind, AssertUnwindSafe},
|
|
|
|
path::Path,
|
2024-01-17 16:40:59 +01:00
|
|
|
path::PathBuf,
|
Only run $env.PROMPT_COMMAND once per prompt (copy of #10986) (#11366)
<!--
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.
-->
This PR is basically a copy of #10986 by @CAD97, which made
`$env.PROMPT_COMMAND` only run once per prompt, but @fdncred found an
issue where hitting Enter would make the transient prompt appear and be
immediately overwritten by the regular prompt, so it was
[reverted](https://github.com/nushell/nushell/pull/11340).
https://github.com/nushell/nushell/pull/10788 was also made to do the
same thing as #10986 but that ended up having the same issue. For some
reason, this branch doesn't have that problem, although I haven't
figured out why yet.
@CAD97, if you have any inputs or want to make your own PR, let me know.
# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->
When hitting enter, the prompt shouldn't blink in place anymore.
# 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 std testing; testing run-tests --path
crates/nu-std"` 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
> ```
-->
# 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.
-->
2023-12-18 16:33:04 +01:00
|
|
|
sync::atomic::Ordering,
|
2024-01-31 18:32:19 +01:00
|
|
|
time::{Duration, Instant},
|
2022-09-19 16:28:36 +02:00
|
|
|
};
|
2024-01-05 12:31:29 +01:00
|
|
|
use sysinfo::System;
|
2022-01-18 09:48:28 +01:00
|
|
|
|
2022-07-20 22:03:29 +02:00
|
|
|
// According to Daniel Imms @Tyriar, we need to do these this way:
|
|
|
|
// <133 A><prompt><133 B><command><133 C><command output>
|
|
|
|
// These first two have been moved to prompt_update to get as close as possible to the prompt.
|
|
|
|
// const PRE_PROMPT_MARKER: &str = "\x1b]133;A\x1b\\";
|
|
|
|
// const POST_PROMPT_MARKER: &str = "\x1b]133;B\x1b\\";
|
2022-06-03 00:57:19 +02:00
|
|
|
const PRE_EXECUTE_MARKER: &str = "\x1b]133;C\x1b\\";
|
2022-07-20 22:03:29 +02:00
|
|
|
// This one is in get_command_finished_marker() now so we can capture the exit codes properly.
|
|
|
|
// const CMD_FINISHED_MARKER: &str = "\x1b]133;D;{}\x1b\\";
|
2022-04-24 02:53:12 +02:00
|
|
|
const RESET_APPLICATION_MODE: &str = "\x1b[?1l";
|
|
|
|
|
2024-01-31 18:32:19 +01:00
|
|
|
///
|
|
|
|
/// The main REPL loop, including spinning up the prompt itself.
|
|
|
|
///
|
2022-03-16 19:17:06 +01:00
|
|
|
pub fn evaluate_repl(
|
2022-02-19 21:54:43 +01:00
|
|
|
engine_state: &mut EngineState,
|
2022-03-16 19:17:06 +01:00
|
|
|
stack: &mut Stack,
|
2022-06-14 22:53:33 +02:00
|
|
|
nushell_path: &str,
|
2022-08-18 11:25:52 +02:00
|
|
|
prerun_command: Option<Spanned<String>>,
|
2023-05-10 14:05:01 +02:00
|
|
|
load_std_lib: Option<Spanned<String>>,
|
2023-01-24 21:28:59 +01:00
|
|
|
entire_start_time: Instant,
|
2022-02-19 21:54:43 +01:00
|
|
|
) -> Result<()> {
|
fix shell integration markers (#11352)
<!--
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!
-->
Fixes #11260
# 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.
-->
Note: my issue description was a bit wrong. Issue one can't be
reproduced without a config (`shell_integration` isn't on by default,
so..), however the issue itself is still valid
For issue 1, the default prompt needs to know about the
`shell_integration` config, and the markers are added around the default
prompt when that's on.
For issue 2, this is actually related to transient prompts. When
rendering, the markers weren't added like for normal prompts.
After the fix the output do now contain the proper markers:
Reproducing the minimum config here for convenience:
```nu
$env.config = {
show_banner: false
shell_integration: true
}
# $env.PROMPT_COMMAND = {|| "> " }
```
For issue 1, the output looks like:
```
[2.3490236,"o","\u001b[?25l\u001b[21;1H\u001b[21;1H\u001b[J\u001b[38;5;10m\u001b]133;A\u001b\\/home/steven\u001b]133;B\u001b\\\u001b[38;5;14m\u001b[38;5;5m\u001b7\u001b[21;84H12/16/2023 03:31:58 PM\u001b8\u001b[0m\u001b[0m\u001b[1;36mecho\u001b[0m\u001b7\u001b8\u001b[?25h"]
[2.5676293,"o","\u001b[6n"]
[2.571353,"o","\u001b[?25l\u001b[21;1H\u001b[21;1H\u001b[J\u001b[38;5;10m\u001b]133;A\u001b\\\u001b]133;A\u001b\\/home/steven\u001b]133;B\u001b\\\u001b]133;B\u001b\\\u001b[38;5;14m\u001b[38;5;5m\u001b7\u001b[21;84H12/16/2023 03:31:59 PM\u001b8\u001b[0m\u001b[0m\u001b[1;36mecho\u001b[0m\u001b7\u001b8\u001b[?25h\u001b[21;1H\r\n\u001b[21;1H"]
[2.571436,"o","\u001b[?2004l"]
[2.5714657,"o","\u001b]133;C\u001b\\"]
```
in line 3, where enter is pressed, `133 A` and `B` are present.
Same for issue 2 (uncomment the `PROMPT_COMMAND` line in the config):
```
[1.9585224,"o","\u001b[?25l\u001b[21;1H\u001b[21;1H\u001b[J\u001b[38;5;10m\u001b]133;A\u001b\\> \u001b]133;B\u001b\\\u001b[38;5;14m\u001b[38;5;5m\u001b7\u001b[21;84H12/16/2023 03:32:15 PM\u001b8\u001b[0m\u001b[0m\u001b[1;36mecho\u001b[0m\u001b7\u001b8\u001b[?25h"]
[2.453972,"o","\u001b[6n"]
[2.4585786,"o","\u001b[?25l\u001b[21;1H\u001b[21;1H\u001b[J\u001b[38;5;10m\u001b]133;A\u001b\\\u001b]133;A\u001b\\> \u001b]133;B\u001b\\\u001b]133;B\u001b\\\u001b[38;5;14m\u001b[38;5;5m\u001b7\u001b[21;84H12/16/2023 03:32:15 PM\u001b8\u001b[0m\u001b[0m\u001b[1;36mecho\u001b[0m\u001b7\u001b8\u001b[?25h\u001b[21;1H\r\n\u001b[21;1H\u001b[?2004l\u001b]133;C\u001b\\\r\n\u001b]133;D;0\u001b\\\u001b]7;file://Aostro-5468/home/steven\u001b\\\u001b]2;~\u0007\u001b[?1l"]
[2.4669976,"o","\u001b[?2004h\u001b[6n"]
[2.4703515,"o","\u001b[6n"]
[2.4736586,"o","\u001b[?25l\u001b[21;1H\u001b[21;1H\u001b[J\u001b[38;5;10m\u001b]133;A\u001b\\> \u001b]133;B\u001b\\\u001b[38;5;14m\u001b[38;5;5m\u001b7\u001b[21;84H12/16/2023 03:32:15 PM\u001b8\u001b[0m\u001b[0m\u001b7\u001b8\u001b[?25h"]
```
# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->
None user facing changes other than that prompt markers are working
# 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 std testing; testing run-tests --path
crates/nu-std"` 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
> ```
-->
# 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.
-->
2023-12-17 03:12:34 +01:00
|
|
|
let config = engine_state.get_config();
|
|
|
|
let use_color = config.use_ansi_coloring;
|
2022-01-18 09:48:28 +01:00
|
|
|
|
2024-01-31 18:32:19 +01:00
|
|
|
confirm_stdin_is_terminal()?;
|
2022-09-05 13:33:54 +02:00
|
|
|
|
2022-01-18 09:48:28 +01:00
|
|
|
let mut entry_num = 0;
|
|
|
|
|
2024-02-22 19:14:10 +01:00
|
|
|
let nu_prompt = NushellPrompt::new(config.shell_integration);
|
2022-01-18 09:48:28 +01:00
|
|
|
|
2023-01-24 21:28:59 +01:00
|
|
|
let start_time = std::time::Instant::now();
|
2022-01-18 09:48:28 +01:00
|
|
|
// Translate environment variables from Strings to Values
|
2022-03-16 19:17:06 +01:00
|
|
|
if let Some(e) = convert_env_values(engine_state, stack) {
|
2024-01-31 18:32:19 +01:00
|
|
|
report_error_new(engine_state, &e);
|
2022-01-18 09:48:28 +01:00
|
|
|
}
|
2023-01-24 21:28:59 +01:00
|
|
|
perf(
|
|
|
|
"translate env vars",
|
|
|
|
start_time,
|
|
|
|
file!(),
|
|
|
|
line!(),
|
|
|
|
column!(),
|
2023-02-02 00:03:05 +01:00
|
|
|
use_color,
|
2023-01-24 21:28:59 +01:00
|
|
|
);
|
2022-01-18 09:48:28 +01:00
|
|
|
|
2022-02-26 14:57:45 +01:00
|
|
|
// seed env vars
|
2022-01-21 20:50:44 +01:00
|
|
|
stack.add_env_var(
|
|
|
|
"CMD_DURATION_MS".into(),
|
Reduced LOC by replacing several instances of `Value::Int {}`, `Value::Float{}`, `Value::Bool {}`, and `Value::String {}` with `Value::int()`, `Value::float()`, `Value::boolean()` and `Value::string()` (#7412)
# Description
While perusing Value.rs, I noticed the `Value::int()`, `Value::float()`,
`Value::boolean()` and `Value::string()` constructors, which seem
designed to make it easier to construct various Values, but which aren't
used often at all in the codebase. So, using a few find-replaces
regexes, I increased their usage. This reduces overall LOC because
structures like this:
```
Value::Int {
val: a,
span: head
}
```
are changed into
```
Value::int(a, head)
```
and are respected as such by the project's formatter.
There are little readability concerns because the second argument to all
of these is `span`, and it's almost always extremely obvious which is
the span at every callsite.
# User-Facing Changes
None.
# 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 -A
clippy::needless_collect` to check that you're using the standard code
style
- `cargo test --workspace` to check that all tests pass
# 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.
2022-12-09 17:37:51 +01:00
|
|
|
Value::string("0823", Span::unknown()),
|
2022-01-21 20:50:44 +01:00
|
|
|
);
|
|
|
|
|
Reduced LOC by replacing several instances of `Value::Int {}`, `Value::Float{}`, `Value::Bool {}`, and `Value::String {}` with `Value::int()`, `Value::float()`, `Value::boolean()` and `Value::string()` (#7412)
# Description
While perusing Value.rs, I noticed the `Value::int()`, `Value::float()`,
`Value::boolean()` and `Value::string()` constructors, which seem
designed to make it easier to construct various Values, but which aren't
used often at all in the codebase. So, using a few find-replaces
regexes, I increased their usage. This reduces overall LOC because
structures like this:
```
Value::Int {
val: a,
span: head
}
```
are changed into
```
Value::int(a, head)
```
and are respected as such by the project's formatter.
There are little readability concerns because the second argument to all
of these is `span`, and it's almost always extremely obvious which is
the span at every callsite.
# User-Facing Changes
None.
# 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 -A
clippy::needless_collect` to check that you're using the standard code
style
- `cargo test --workspace` to check that all tests pass
# 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.
2022-12-09 17:37:51 +01:00
|
|
|
stack.add_env_var("LAST_EXIT_CODE".into(), Value::int(0, Span::unknown()));
|
2022-02-26 14:57:45 +01:00
|
|
|
|
2024-02-22 19:14:10 +01:00
|
|
|
let mut line_editor = get_line_editor(engine_state, nushell_path, use_color)?;
|
2023-09-29 16:36:03 +02:00
|
|
|
let temp_file = temp_dir().join(format!("{}.nu", uuid::Uuid::new_v4()));
|
2022-09-19 16:28:36 +02:00
|
|
|
|
2022-08-18 11:25:52 +02:00
|
|
|
if let Some(s) = prerun_command {
|
|
|
|
eval_source(
|
|
|
|
engine_state,
|
|
|
|
stack,
|
|
|
|
s.item.as_bytes(),
|
2023-01-30 02:37:54 +01:00
|
|
|
&format!("entry #{entry_num}"),
|
2022-12-07 19:31:57 +01:00
|
|
|
PipelineData::empty(),
|
2023-02-02 00:02:27 +01:00
|
|
|
false,
|
2022-08-18 11:25:52 +02:00
|
|
|
);
|
2023-06-04 21:04:28 +02:00
|
|
|
engine_state.merge_env(stack, get_guaranteed_cwd(engine_state, stack))?;
|
2022-08-18 11:25:52 +02:00
|
|
|
}
|
|
|
|
|
2023-05-10 14:05:01 +02:00
|
|
|
engine_state.set_startup_time(entire_start_time.elapsed().as_nanos() as i64);
|
|
|
|
|
2023-09-01 08:18:55 +02:00
|
|
|
// Regenerate the $nu constant to contain the startup time and any other potential updates
|
|
|
|
let nu_const = create_nu_constant(engine_state, Span::unknown())?;
|
|
|
|
engine_state.set_variable_const_val(NU_VARIABLE_ID, nu_const);
|
|
|
|
|
2023-05-10 14:05:01 +02:00
|
|
|
if load_std_lib.is_none() && engine_state.get_config().show_banner {
|
|
|
|
eval_source(
|
|
|
|
engine_state,
|
|
|
|
stack,
|
|
|
|
r#"use std banner; banner"#.as_bytes(),
|
|
|
|
"show_banner",
|
|
|
|
PipelineData::empty(),
|
|
|
|
false,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2024-01-31 18:32:19 +01:00
|
|
|
kitty_protocol_healthcheck(engine_state);
|
Add kitty protocol config to nushell (#10540)
# Description
Support keyboard enhancement protocol as implemented by Kitty console,
hence Kitty protocol.
This PR enables Nushell to use keybinding that is not available before,
such as Ctrl+i (that alias to Tab) or Ctrl+e (that alias to Esc, likely
I mistaken). After this PR merged and you set `use_kitty_protocol`
enabled, if your console app support Kitty protocol (WezTerm, Kitty,
etc.) you will be able to set more fine-grained keybinding.
For Colemak users, this feature is a blessing, because some Ctrl+[hjkl]
that previously unmap-able to Ctlr+[hnei] now it is.
# User-Facing Changes
This adds `use_kitty_protocol` config which defaults to false. When set
to `true`, it enables kitty protocol on the line editor when supported,
or else it warns.
---------
Co-authored-by: Stefan Holderbach <sholderbach@users.noreply.github.com>
2023-09-29 15:52:34 +02:00
|
|
|
|
2024-02-22 19:14:10 +01:00
|
|
|
// Setup initial engine_state and stack state
|
|
|
|
let mut previous_engine_state = engine_state.clone();
|
|
|
|
let mut previous_stack = stack.clone();
|
2022-01-18 09:48:28 +01:00
|
|
|
loop {
|
2024-02-22 19:14:10 +01:00
|
|
|
// clone these values so that they can be moved by AssertUnwindSafe
|
|
|
|
// If there is a panic within this iteration the last engine_state and stack
|
|
|
|
// will be used
|
|
|
|
let mut current_engine_state = previous_engine_state.clone();
|
|
|
|
let mut current_stack = previous_stack.clone();
|
|
|
|
let temp_file_cloned = temp_file.clone();
|
|
|
|
let mut nu_prompt_cloned = nu_prompt.clone();
|
|
|
|
|
|
|
|
match catch_unwind(AssertUnwindSafe(move || {
|
|
|
|
match loop_iteration(
|
|
|
|
&mut current_engine_state,
|
|
|
|
&mut current_stack,
|
|
|
|
line_editor,
|
|
|
|
&mut nu_prompt_cloned,
|
|
|
|
&temp_file_cloned,
|
|
|
|
use_color,
|
|
|
|
&mut entry_num,
|
|
|
|
) {
|
|
|
|
// pass the most recent version of the line_editor back
|
|
|
|
Ok((continue_loop, line_editor)) => (
|
|
|
|
Ok(continue_loop),
|
|
|
|
current_engine_state,
|
|
|
|
current_stack,
|
|
|
|
line_editor,
|
|
|
|
),
|
|
|
|
Err(e) => {
|
|
|
|
current_engine_state.recover_from_panic();
|
|
|
|
(
|
|
|
|
Err(e),
|
|
|
|
current_engine_state,
|
|
|
|
current_stack,
|
|
|
|
Reedline::create(),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})) {
|
|
|
|
Ok((result, es, s, le)) => {
|
|
|
|
// setup state for the next iteration of the repl loop
|
|
|
|
previous_engine_state = es;
|
|
|
|
previous_stack = s;
|
|
|
|
line_editor = le;
|
|
|
|
match result {
|
|
|
|
Ok(false) => {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
Err(e) => {
|
|
|
|
return Err(e);
|
|
|
|
}
|
|
|
|
_ => (),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Err(_) => {
|
|
|
|
// line_editor is lost in the error case so reconstruct a new one
|
|
|
|
line_editor = get_line_editor(engine_state, nushell_path, use_color)?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-02-10 22:22:39 +01:00
|
|
|
|
2024-02-22 19:14:10 +01:00
|
|
|
Ok(())
|
|
|
|
}
|
2023-06-04 21:04:28 +02:00
|
|
|
|
2024-02-22 19:14:10 +01:00
|
|
|
fn get_line_editor(
|
|
|
|
engine_state: &mut EngineState,
|
|
|
|
nushell_path: &str,
|
|
|
|
use_color: bool,
|
|
|
|
) -> Result<Reedline> {
|
|
|
|
let mut start_time = std::time::Instant::now();
|
|
|
|
let mut line_editor = Reedline::create();
|
2022-07-14 16:09:27 +02:00
|
|
|
|
2024-02-22 19:14:10 +01:00
|
|
|
// Now that reedline is created, get the history session id and store it in engine_state
|
|
|
|
store_history_id_in_engine(engine_state, &line_editor);
|
|
|
|
perf(
|
|
|
|
"setup reedline",
|
|
|
|
start_time,
|
|
|
|
file!(),
|
|
|
|
line!(),
|
|
|
|
column!(),
|
|
|
|
use_color,
|
|
|
|
);
|
2023-01-24 21:28:59 +01:00
|
|
|
|
2024-02-22 19:14:10 +01:00
|
|
|
if let Some(history) = engine_state.history_config() {
|
2023-01-24 21:28:59 +01:00
|
|
|
start_time = std::time::Instant::now();
|
2022-04-11 20:19:42 +02:00
|
|
|
|
2024-02-22 19:14:10 +01:00
|
|
|
line_editor = setup_history(nushell_path, engine_state, line_editor, history)?;
|
2023-01-13 21:37:39 +01:00
|
|
|
|
2023-01-24 21:28:59 +01:00
|
|
|
perf(
|
2024-02-22 19:14:10 +01:00
|
|
|
"setup history",
|
2023-01-24 21:28:59 +01:00
|
|
|
start_time,
|
|
|
|
file!(),
|
|
|
|
line!(),
|
|
|
|
column!(),
|
2023-02-02 00:03:05 +01:00
|
|
|
use_color,
|
2023-01-24 21:28:59 +01:00
|
|
|
);
|
2024-02-22 19:14:10 +01:00
|
|
|
}
|
|
|
|
Ok(line_editor)
|
|
|
|
}
|
2023-01-24 21:28:59 +01:00
|
|
|
|
2024-02-22 19:14:10 +01:00
|
|
|
///
|
|
|
|
/// Perform one iteration of the REPL loop
|
|
|
|
/// Result is bool: continue loop, current reedline
|
|
|
|
#[inline]
|
|
|
|
fn loop_iteration(
|
|
|
|
engine_state: &mut EngineState,
|
|
|
|
stack: &mut Stack,
|
|
|
|
line_editor: Reedline,
|
|
|
|
nu_prompt: &mut NushellPrompt,
|
|
|
|
temp_file: &Path,
|
|
|
|
use_color: bool,
|
|
|
|
entry_num: &mut usize,
|
|
|
|
) -> Result<(bool, Reedline)> {
|
|
|
|
use nu_cmd_base::hook;
|
|
|
|
use reedline::Signal;
|
|
|
|
let loop_start_time = std::time::Instant::now();
|
2023-01-13 21:37:39 +01:00
|
|
|
|
2024-02-22 19:14:10 +01:00
|
|
|
let cwd = get_guaranteed_cwd(engine_state, stack);
|
2022-01-27 08:53:23 +01:00
|
|
|
|
2024-02-22 19:14:10 +01:00
|
|
|
let mut start_time = std::time::Instant::now();
|
|
|
|
// Before doing anything, merge the environment from the previous REPL iteration into the
|
|
|
|
// permanent state.
|
|
|
|
if let Err(err) = engine_state.merge_env(stack, cwd) {
|
|
|
|
report_error_new(engine_state, &err);
|
|
|
|
}
|
|
|
|
perf(
|
|
|
|
"merge env",
|
|
|
|
start_time,
|
|
|
|
file!(),
|
|
|
|
line!(),
|
|
|
|
column!(),
|
|
|
|
use_color,
|
|
|
|
);
|
color_config now accepts closures as color values (#7141)
# Description
Closes #6909. You can now add closures to your `color_config` themes.
Whenever a value would be printed with `table`, the closure is run with
the value piped-in. The closure must return either a {fg,bg,attr} record
or a color name (`'light_red'` etc.). This returned style is used to
colour the value.
This is entirely backwards-compatible with existing config.nu files.
Example code excerpt:
```
let my_theme = {
header: green_bold
bool: { if $in { 'light_cyan' } else { 'light_red' } }
int: purple_bold
filesize: { |e| if $e == 0b { 'gray' } else if $e < 1mb { 'purple_bold' } else { 'cyan_bold' } }
duration: purple_bold
date: { (date now) - $in | if $in > 1wk { 'cyan_bold' } else if $in > 1day { 'green_bold' } else { 'yellow_bold' } }
range: yellow_bold
string: { if $in =~ '^#\w{6}$' { $in } else { 'white' } }
nothing: white
```
Example output with this in effect:
![2022-11-16 12 47 23 AM - style_computer
rs_-_nushell_-_VSCodium](https://user-images.githubusercontent.com/83939/201952558-482de05d-69c7-4bf2-91fc-d0964bf71264.png)
![2022-11-16 12 39 41 AM - style_computer
rs_-_nushell_-_VSCodium](https://user-images.githubusercontent.com/83939/201952580-2384bb86-b680-40fe-8192-71bae396c738.png)
![2022-11-15 09 21 54 PM - run_external
rs_-_nushell_-_VSCodium](https://user-images.githubusercontent.com/83939/201952601-343fc15d-e4a8-4a92-ad89-9a7d17d42748.png)
Slightly important notes:
* Some color_config names, namely "separator", "empty" and "hints", pipe
in `null` instead of a value.
* Currently, doing anything non-trivial inside a closure has an
understandably big perf hit. I currently do not actually recommend
something like `string: { if $in =~ '^#\w{6}$' { $in } else { 'white' }
}` for serious work, mainly because of the abundance of string-type data
in the world. Nevertheless, lesser-used types like "date" and "duration"
work well with this.
* I had to do some reorganisation in order to make it possible to call
`eval_block()` that late in table rendering. I invented a new struct
called "StyleComputer" which holds the engine_state and stack of the
initial `table` command (implicit or explicit).
* StyleComputer has a `compute()` method which takes a color_config name
and a nu value, and always returns the correct Style, so you don't have
to worry about A) the color_config value was set at all, B) whether it
was set to a closure or not, or C) which default style to use in those
cases.
* Currently, errors encountered during execution of the closures are
thrown in the garbage. Any other ideas are welcome. (Nonetheless, errors
result in a huge perf hit when they are encountered. I think what should
be done is to assume something terrible happened to the user's config
and invalidate the StyleComputer for that `table` run, thus causing
subsequent output to just be Style::default().)
* More thorough tests are forthcoming - ran into some difficulty using
`nu!` to take an alternative config, and for some reason `let-env config
=` statements don't seem to work inside `nu!` pipelines(???)
* The default config.nu has not been updated to make use of this yet. Do
tell if you think I should incorporate that into this.
# User-Facing Changes
See above.
# 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 --features=extra -- -D warnings -D
clippy::unwrap_used -A clippy::needless_collect` to check that you're
using the standard code style
- `cargo test --workspace --features=extra` to check that all tests pass
# 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.
2022-12-17 14:07:56 +01:00
|
|
|
|
2024-02-22 19:14:10 +01:00
|
|
|
start_time = std::time::Instant::now();
|
|
|
|
//Reset the ctrl-c handler
|
|
|
|
if let Some(ctrlc) = &mut engine_state.ctrlc {
|
|
|
|
ctrlc.store(false, Ordering::SeqCst);
|
|
|
|
}
|
|
|
|
perf(
|
|
|
|
"reset ctrlc",
|
|
|
|
start_time,
|
|
|
|
file!(),
|
|
|
|
line!(),
|
|
|
|
column!(),
|
|
|
|
use_color,
|
|
|
|
);
|
2022-02-10 22:22:39 +01:00
|
|
|
|
2024-02-22 19:14:10 +01:00
|
|
|
start_time = std::time::Instant::now();
|
|
|
|
let config = engine_state.get_config();
|
2022-03-27 15:01:04 +02:00
|
|
|
|
2024-02-22 19:14:10 +01:00
|
|
|
let engine_reference = std::sync::Arc::new(engine_state.clone());
|
2022-04-30 16:40:41 +02:00
|
|
|
|
2024-02-22 19:14:10 +01:00
|
|
|
// Find the configured cursor shapes for each mode
|
|
|
|
let cursor_config = CursorConfig {
|
|
|
|
vi_insert: map_nucursorshape_to_cursorshape(config.cursor_shape_vi_insert),
|
|
|
|
vi_normal: map_nucursorshape_to_cursorshape(config.cursor_shape_vi_normal),
|
|
|
|
emacs: map_nucursorshape_to_cursorshape(config.cursor_shape_emacs),
|
|
|
|
};
|
|
|
|
perf(
|
|
|
|
"get config/cursor config",
|
|
|
|
start_time,
|
|
|
|
file!(),
|
|
|
|
line!(),
|
|
|
|
column!(),
|
|
|
|
use_color,
|
|
|
|
);
|
2022-01-18 09:48:28 +01:00
|
|
|
|
2024-02-22 19:14:10 +01:00
|
|
|
start_time = std::time::Instant::now();
|
|
|
|
|
|
|
|
let mut line_editor = line_editor
|
|
|
|
.use_kitty_keyboard_enhancement(config.use_kitty_protocol)
|
|
|
|
// try to enable bracketed paste
|
|
|
|
// It doesn't work on windows system: https://github.com/crossterm-rs/crossterm/issues/737
|
|
|
|
.use_bracketed_paste(cfg!(not(target_os = "windows")) && config.bracketed_paste)
|
|
|
|
.with_highlighter(Box::new(NuHighlighter {
|
|
|
|
engine_state: engine_reference.clone(),
|
|
|
|
stack: std::sync::Arc::new(stack.clone()),
|
|
|
|
config: config.clone(),
|
|
|
|
}))
|
|
|
|
.with_validator(Box::new(NuValidator {
|
|
|
|
engine_state: engine_reference.clone(),
|
|
|
|
}))
|
|
|
|
.with_completer(Box::new(NuCompleter::new(
|
|
|
|
engine_reference.clone(),
|
|
|
|
stack.clone(),
|
|
|
|
)))
|
|
|
|
.with_quick_completions(config.quick_completions)
|
|
|
|
.with_partial_completions(config.partial_completions)
|
|
|
|
.with_ansi_colors(config.use_ansi_coloring)
|
|
|
|
.with_cursor_config(cursor_config);
|
|
|
|
perf(
|
|
|
|
"reedline builder",
|
|
|
|
start_time,
|
|
|
|
file!(),
|
|
|
|
line!(),
|
|
|
|
column!(),
|
|
|
|
use_color,
|
|
|
|
);
|
|
|
|
|
|
|
|
let style_computer = StyleComputer::from_config(engine_state, stack);
|
|
|
|
|
|
|
|
start_time = std::time::Instant::now();
|
|
|
|
line_editor = if config.use_ansi_coloring {
|
|
|
|
line_editor.with_hinter(Box::new({
|
|
|
|
// As of Nov 2022, "hints" color_config closures only get `null` passed in.
|
|
|
|
let style = style_computer.compute("hints", &Value::nothing(Span::unknown()));
|
|
|
|
CwdAwareHinter::default().with_style(style)
|
|
|
|
}))
|
|
|
|
} else {
|
|
|
|
line_editor.disable_hints()
|
|
|
|
};
|
|
|
|
perf(
|
|
|
|
"reedline coloring/style_computer",
|
|
|
|
start_time,
|
|
|
|
file!(),
|
|
|
|
line!(),
|
|
|
|
column!(),
|
|
|
|
use_color,
|
|
|
|
);
|
|
|
|
|
|
|
|
start_time = std::time::Instant::now();
|
|
|
|
line_editor = add_menus(line_editor, engine_reference, stack, config).unwrap_or_else(|e| {
|
|
|
|
report_error_new(engine_state, &e);
|
|
|
|
Reedline::create()
|
|
|
|
});
|
|
|
|
perf(
|
|
|
|
"reedline menus",
|
|
|
|
start_time,
|
|
|
|
file!(),
|
|
|
|
line!(),
|
|
|
|
column!(),
|
|
|
|
use_color,
|
|
|
|
);
|
|
|
|
|
|
|
|
start_time = std::time::Instant::now();
|
|
|
|
let buffer_editor = get_editor(engine_state, stack, Span::unknown());
|
|
|
|
|
|
|
|
line_editor = if let Ok((cmd, args)) = buffer_editor {
|
|
|
|
let mut command = std::process::Command::new(cmd);
|
|
|
|
command
|
|
|
|
.args(args)
|
|
|
|
.envs(env_to_strings(engine_state, stack)?);
|
|
|
|
line_editor.with_buffer_editor(command, temp_file.to_path_buf())
|
|
|
|
} else {
|
|
|
|
line_editor
|
|
|
|
};
|
|
|
|
perf(
|
|
|
|
"reedline buffer_editor",
|
|
|
|
start_time,
|
|
|
|
file!(),
|
|
|
|
line!(),
|
|
|
|
column!(),
|
|
|
|
use_color,
|
|
|
|
);
|
2022-01-18 09:48:28 +01:00
|
|
|
|
2024-02-22 19:14:10 +01:00
|
|
|
if let Some(history) = engine_state.history_config() {
|
2023-01-24 21:28:59 +01:00
|
|
|
start_time = std::time::Instant::now();
|
2024-02-22 19:14:10 +01:00
|
|
|
if history.sync_on_enter {
|
|
|
|
if let Err(e) = line_editor.sync_history() {
|
|
|
|
warn!("Failed to sync history: {}", e);
|
2022-05-09 03:56:48 +02:00
|
|
|
}
|
|
|
|
}
|
2023-02-02 00:03:05 +01:00
|
|
|
perf(
|
2024-02-22 19:14:10 +01:00
|
|
|
"sync_history",
|
2023-02-02 00:03:05 +01:00
|
|
|
start_time,
|
|
|
|
file!(),
|
|
|
|
line!(),
|
|
|
|
column!(),
|
|
|
|
use_color,
|
|
|
|
);
|
2024-02-22 19:14:10 +01:00
|
|
|
}
|
2022-05-09 03:56:48 +02:00
|
|
|
|
2024-02-22 19:14:10 +01:00
|
|
|
start_time = std::time::Instant::now();
|
|
|
|
// Changing the line editor based on the found keybindings
|
|
|
|
line_editor = setup_keybindings(engine_state, line_editor);
|
|
|
|
perf(
|
|
|
|
"keybindings",
|
|
|
|
start_time,
|
|
|
|
file!(),
|
|
|
|
line!(),
|
|
|
|
column!(),
|
|
|
|
use_color,
|
|
|
|
);
|
|
|
|
|
|
|
|
start_time = std::time::Instant::now();
|
|
|
|
// Right before we start our prompt and take input from the user,
|
|
|
|
// fire the "pre_prompt" hook
|
|
|
|
if let Some(hook) = config.hooks.pre_prompt.clone() {
|
|
|
|
if let Err(err) = eval_hook(engine_state, stack, None, vec![], &hook, "pre_prompt") {
|
|
|
|
report_error_new(engine_state, &err);
|
2022-05-20 23:49:42 +02:00
|
|
|
}
|
2024-02-22 19:14:10 +01:00
|
|
|
}
|
|
|
|
perf(
|
|
|
|
"pre-prompt hook",
|
|
|
|
start_time,
|
|
|
|
file!(),
|
|
|
|
line!(),
|
|
|
|
column!(),
|
|
|
|
use_color,
|
|
|
|
);
|
2022-05-20 23:49:42 +02:00
|
|
|
|
2024-02-22 19:14:10 +01:00
|
|
|
start_time = std::time::Instant::now();
|
|
|
|
// Next, check all the environment variables they ask for
|
|
|
|
// fire the "env_change" hook
|
|
|
|
let config = engine_state.get_config();
|
|
|
|
if let Err(error) =
|
|
|
|
hook::eval_env_change_hook(config.hooks.env_change.clone(), engine_state, stack)
|
|
|
|
{
|
|
|
|
report_error_new(engine_state, &error)
|
|
|
|
}
|
|
|
|
perf(
|
|
|
|
"env-change hook",
|
|
|
|
start_time,
|
|
|
|
file!(),
|
|
|
|
line!(),
|
|
|
|
column!(),
|
|
|
|
use_color,
|
|
|
|
);
|
2022-01-18 09:48:28 +01:00
|
|
|
|
2024-02-22 19:14:10 +01:00
|
|
|
start_time = std::time::Instant::now();
|
|
|
|
let config = &engine_state.get_config().clone();
|
|
|
|
prompt_update::update_prompt(config, engine_state, stack, nu_prompt);
|
|
|
|
let transient_prompt =
|
|
|
|
prompt_update::make_transient_prompt(config, engine_state, stack, nu_prompt);
|
|
|
|
perf(
|
|
|
|
"update_prompt",
|
|
|
|
start_time,
|
|
|
|
file!(),
|
|
|
|
line!(),
|
|
|
|
column!(),
|
|
|
|
use_color,
|
|
|
|
);
|
2022-02-10 22:22:39 +01:00
|
|
|
|
2024-02-22 19:14:10 +01:00
|
|
|
*entry_num += 1;
|
2022-06-14 22:53:33 +02:00
|
|
|
|
2024-02-22 19:14:10 +01:00
|
|
|
start_time = std::time::Instant::now();
|
|
|
|
line_editor = line_editor.with_transient_prompt(transient_prompt);
|
|
|
|
let input = line_editor.read_line(nu_prompt);
|
|
|
|
let shell_integration = config.shell_integration;
|
|
|
|
|
|
|
|
match input {
|
|
|
|
Ok(Signal::Success(s)) => {
|
|
|
|
let hostname = System::host_name();
|
|
|
|
let history_supports_meta = matches!(
|
|
|
|
engine_state.history_config().map(|h| h.file_format),
|
|
|
|
Some(HistoryFileFormat::Sqlite)
|
|
|
|
);
|
|
|
|
|
|
|
|
if history_supports_meta {
|
|
|
|
prepare_history_metadata(&s, &hostname, engine_state, &mut line_editor)?;
|
|
|
|
}
|
2022-05-08 21:28:39 +02:00
|
|
|
|
2024-02-22 19:14:10 +01:00
|
|
|
// Right before we start running the code the user gave us, fire the `pre_execution`
|
|
|
|
// hook
|
|
|
|
if let Some(hook) = config.hooks.pre_execution.clone() {
|
|
|
|
// Set the REPL buffer to the current command for the "pre_execution" hook
|
2023-06-11 00:38:11 +02:00
|
|
|
let mut repl = engine_state.repl_state.lock().expect("repl state mutex");
|
2024-02-22 19:14:10 +01:00
|
|
|
repl.buffer = s.to_string();
|
2023-06-11 00:38:11 +02:00
|
|
|
drop(repl);
|
2023-03-22 12:43:25 +01:00
|
|
|
|
2024-02-22 19:14:10 +01:00
|
|
|
if let Err(err) =
|
|
|
|
eval_hook(engine_state, stack, None, vec![], &hook, "pre_execution")
|
|
|
|
{
|
|
|
|
report_error_new(engine_state, &err);
|
2022-05-10 23:33:18 +02:00
|
|
|
}
|
2024-02-22 19:14:10 +01:00
|
|
|
}
|
2022-05-10 23:33:18 +02:00
|
|
|
|
2024-02-22 19:14:10 +01:00
|
|
|
let mut repl = engine_state.repl_state.lock().expect("repl state mutex");
|
|
|
|
repl.cursor_pos = line_editor.current_insertion_point();
|
|
|
|
repl.buffer = line_editor.current_buffer_contents().to_string();
|
|
|
|
drop(repl);
|
2024-01-31 18:32:19 +01:00
|
|
|
|
2024-02-22 19:14:10 +01:00
|
|
|
if shell_integration {
|
|
|
|
run_ansi_sequence(PRE_EXECUTE_MARKER)?;
|
|
|
|
}
|
2022-05-05 17:10:03 +02:00
|
|
|
|
2024-02-22 19:14:10 +01:00
|
|
|
// Actual command execution logic starts from here
|
|
|
|
let start_time = Instant::now();
|
2022-05-05 17:10:03 +02:00
|
|
|
|
2024-02-22 19:14:10 +01:00
|
|
|
match parse_operation(s.clone(), engine_state, stack)? {
|
|
|
|
ReplOperation::AutoCd { cwd, target, span } => {
|
|
|
|
do_auto_cd(target, cwd, stack, engine_state, span);
|
|
|
|
}
|
|
|
|
ReplOperation::RunCommand(cmd) => {
|
|
|
|
line_editor = do_run_cmd(
|
|
|
|
&cmd,
|
2024-01-31 18:32:19 +01:00
|
|
|
stack,
|
2024-02-22 19:14:10 +01:00
|
|
|
engine_state,
|
|
|
|
line_editor,
|
|
|
|
shell_integration,
|
|
|
|
*entry_num,
|
2024-01-31 18:32:19 +01:00
|
|
|
)?;
|
2022-06-14 22:53:33 +02:00
|
|
|
}
|
2024-02-22 19:14:10 +01:00
|
|
|
// as the name implies, we do nothing in this case
|
|
|
|
ReplOperation::DoNothing => {}
|
|
|
|
}
|
2022-06-14 22:53:33 +02:00
|
|
|
|
2024-02-22 19:14:10 +01:00
|
|
|
let cmd_duration = start_time.elapsed();
|
2022-09-09 22:31:32 +02:00
|
|
|
|
2024-02-22 19:14:10 +01:00
|
|
|
stack.add_env_var(
|
|
|
|
"CMD_DURATION_MS".into(),
|
|
|
|
Value::string(format!("{}", cmd_duration.as_millis()), Span::unknown()),
|
|
|
|
);
|
|
|
|
|
|
|
|
if history_supports_meta {
|
|
|
|
fill_in_result_related_history_metadata(
|
|
|
|
&s,
|
|
|
|
engine_state,
|
|
|
|
cmd_duration,
|
|
|
|
stack,
|
|
|
|
&mut line_editor,
|
|
|
|
)?;
|
2022-01-18 09:48:28 +01:00
|
|
|
}
|
2024-02-22 19:14:10 +01:00
|
|
|
|
|
|
|
if shell_integration {
|
|
|
|
do_shell_integration_finalize_command(hostname, engine_state, stack)?;
|
2022-01-18 09:48:28 +01:00
|
|
|
}
|
2024-02-22 19:14:10 +01:00
|
|
|
|
|
|
|
flush_engine_state_repl_buffer(engine_state, &mut line_editor);
|
|
|
|
}
|
|
|
|
Ok(Signal::CtrlC) => {
|
|
|
|
// `Reedline` clears the line content. New prompt is shown
|
|
|
|
if shell_integration {
|
|
|
|
run_ansi_sequence(&get_command_finished_marker(stack, engine_state))?;
|
2022-01-18 09:48:28 +01:00
|
|
|
}
|
2024-02-22 19:14:10 +01:00
|
|
|
}
|
|
|
|
Ok(Signal::CtrlD) => {
|
|
|
|
// When exiting clear to a new line
|
|
|
|
if shell_integration {
|
|
|
|
run_ansi_sequence(&get_command_finished_marker(stack, engine_state))?;
|
|
|
|
}
|
|
|
|
println!();
|
|
|
|
return Ok((false, line_editor));
|
|
|
|
}
|
|
|
|
Err(err) => {
|
|
|
|
let message = err.to_string();
|
|
|
|
if !message.contains("duration") {
|
|
|
|
eprintln!("Error: {err:?}");
|
|
|
|
// TODO: Identify possible error cases where a hard failure is preferable
|
|
|
|
// Ignoring and reporting could hide bigger problems
|
|
|
|
// e.g. https://github.com/nushell/nushell/issues/6452
|
|
|
|
// Alternatively only allow that expected failures let the REPL loop
|
|
|
|
}
|
|
|
|
if shell_integration {
|
|
|
|
run_ansi_sequence(&get_command_finished_marker(stack, engine_state))?;
|
2022-01-18 09:48:28 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-02-22 19:14:10 +01:00
|
|
|
perf(
|
|
|
|
"processing line editor input",
|
|
|
|
start_time,
|
|
|
|
file!(),
|
|
|
|
line!(),
|
|
|
|
column!(),
|
|
|
|
use_color,
|
|
|
|
);
|
2022-01-18 09:48:28 +01:00
|
|
|
|
2024-02-22 19:14:10 +01:00
|
|
|
perf(
|
|
|
|
"finished repl loop",
|
|
|
|
loop_start_time,
|
|
|
|
file!(),
|
|
|
|
line!(),
|
|
|
|
column!(),
|
|
|
|
use_color,
|
|
|
|
);
|
|
|
|
|
|
|
|
Ok((true, line_editor))
|
2022-01-18 09:48:28 +01:00
|
|
|
}
|
2022-05-08 21:28:39 +02:00
|
|
|
|
2024-01-31 18:32:19 +01:00
|
|
|
///
|
|
|
|
/// Put in history metadata not related to the result of running the command
|
|
|
|
///
|
|
|
|
fn prepare_history_metadata(
|
|
|
|
s: &str,
|
|
|
|
hostname: &Option<String>,
|
|
|
|
engine_state: &EngineState,
|
|
|
|
line_editor: &mut Reedline,
|
|
|
|
) -> Result<()> {
|
|
|
|
if !s.is_empty() && line_editor.has_last_command_context() {
|
|
|
|
line_editor
|
|
|
|
.update_last_command_context(&|mut c| {
|
|
|
|
c.start_timestamp = Some(chrono::Utc::now());
|
|
|
|
c.hostname = hostname.clone();
|
|
|
|
|
|
|
|
c.cwd = Some(StateWorkingSet::new(engine_state).get_cwd());
|
|
|
|
c
|
|
|
|
})
|
|
|
|
.into_diagnostic()?; // todo: don't stop repl if error here?
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
///
|
|
|
|
/// Fills in history item metadata based on the execution result (notably duration and exit code)
|
|
|
|
///
|
|
|
|
fn fill_in_result_related_history_metadata(
|
|
|
|
s: &str,
|
|
|
|
engine_state: &EngineState,
|
|
|
|
cmd_duration: Duration,
|
|
|
|
stack: &mut Stack,
|
|
|
|
line_editor: &mut Reedline,
|
|
|
|
) -> Result<()> {
|
|
|
|
if !s.is_empty() && line_editor.has_last_command_context() {
|
|
|
|
line_editor
|
|
|
|
.update_last_command_context(&|mut c| {
|
|
|
|
c.duration = Some(cmd_duration);
|
|
|
|
c.exit_status = stack
|
|
|
|
.get_env_var(engine_state, "LAST_EXIT_CODE")
|
|
|
|
.and_then(|e| e.as_i64().ok());
|
|
|
|
c
|
|
|
|
})
|
|
|
|
.into_diagnostic()?; // todo: don't stop repl if error here?
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// The kinds of operations you can do in a single loop iteration of the REPL
|
|
|
|
enum ReplOperation {
|
|
|
|
/// "auto-cd": change directory by typing it in directly
|
|
|
|
AutoCd {
|
|
|
|
/// the current working directory
|
|
|
|
cwd: String,
|
|
|
|
/// the target
|
|
|
|
target: PathBuf,
|
|
|
|
/// span information for debugging
|
|
|
|
span: Span,
|
|
|
|
},
|
|
|
|
/// run a command
|
|
|
|
RunCommand(String),
|
|
|
|
/// do nothing (usually through an empty string)
|
|
|
|
DoNothing,
|
|
|
|
}
|
|
|
|
|
|
|
|
///
|
|
|
|
/// Parses one "REPL line" of input, to try and derive intent.
|
|
|
|
/// Notably, this is where we detect whether the user is attempting an
|
|
|
|
/// "auto-cd" (writing a relative path directly instead of `cd path`)
|
|
|
|
///
|
|
|
|
/// Returns the ReplOperation we believe the user wants to do
|
|
|
|
///
|
|
|
|
fn parse_operation(
|
|
|
|
s: String,
|
|
|
|
engine_state: &EngineState,
|
|
|
|
stack: &Stack,
|
|
|
|
) -> Result<ReplOperation, ErrReport> {
|
|
|
|
let tokens = lex(s.as_bytes(), 0, &[], &[], false);
|
|
|
|
// Check if this is a single call to a directory, if so auto-cd
|
|
|
|
let cwd = nu_engine::env::current_dir_str(engine_state, stack)?;
|
|
|
|
let mut orig = s.clone();
|
|
|
|
if orig.starts_with('`') {
|
|
|
|
orig = trim_quotes_str(&orig).to_string()
|
|
|
|
}
|
|
|
|
|
|
|
|
let path = nu_path::expand_path_with(&orig, &cwd);
|
|
|
|
if looks_like_path(&orig) && path.is_dir() && tokens.0.len() == 1 {
|
|
|
|
Ok(ReplOperation::AutoCd {
|
|
|
|
cwd,
|
|
|
|
target: path,
|
|
|
|
span: tokens.0[0].span,
|
|
|
|
})
|
|
|
|
} else if !s.trim().is_empty() {
|
|
|
|
Ok(ReplOperation::RunCommand(s))
|
|
|
|
} else {
|
|
|
|
Ok(ReplOperation::DoNothing)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
///
|
|
|
|
/// Execute an "auto-cd" operation, changing the current working directory.
|
|
|
|
///
|
|
|
|
fn do_auto_cd(
|
|
|
|
path: PathBuf,
|
|
|
|
cwd: String,
|
|
|
|
stack: &mut Stack,
|
|
|
|
engine_state: &mut EngineState,
|
|
|
|
span: Span,
|
|
|
|
) {
|
|
|
|
let path = {
|
|
|
|
if !path.exists() {
|
|
|
|
report_error_new(
|
|
|
|
engine_state,
|
|
|
|
&ShellError::DirectoryNotFound {
|
|
|
|
dir: path.to_string_lossy().to_string(),
|
|
|
|
span,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
|
|
|
let path = nu_path::canonicalize_with(path, &cwd)
|
|
|
|
.expect("internal error: cannot canonicalize known path");
|
|
|
|
path.to_string_lossy().to_string()
|
|
|
|
};
|
|
|
|
|
|
|
|
stack.add_env_var("OLDPWD".into(), Value::string(cwd.clone(), Span::unknown()));
|
|
|
|
|
|
|
|
//FIXME: this only changes the current scope, but instead this environment variable
|
|
|
|
//should probably be a block that loads the information from the state in the overlay
|
|
|
|
stack.add_env_var("PWD".into(), Value::string(path.clone(), Span::unknown()));
|
|
|
|
let cwd = Value::string(cwd, span);
|
|
|
|
|
|
|
|
let shells = stack.get_env_var(engine_state, "NUSHELL_SHELLS");
|
|
|
|
let mut shells = if let Some(v) = shells {
|
2024-02-17 19:14:16 +01:00
|
|
|
v.into_list().unwrap_or_else(|_| vec![cwd])
|
2024-01-31 18:32:19 +01:00
|
|
|
} else {
|
|
|
|
vec![cwd]
|
|
|
|
};
|
|
|
|
|
|
|
|
let current_shell = stack.get_env_var(engine_state, "NUSHELL_CURRENT_SHELL");
|
|
|
|
let current_shell = if let Some(v) = current_shell {
|
|
|
|
v.as_int().unwrap_or_default() as usize
|
|
|
|
} else {
|
|
|
|
0
|
|
|
|
};
|
|
|
|
|
|
|
|
let last_shell = stack.get_env_var(engine_state, "NUSHELL_LAST_SHELL");
|
|
|
|
let last_shell = if let Some(v) = last_shell {
|
|
|
|
v.as_int().unwrap_or_default() as usize
|
|
|
|
} else {
|
|
|
|
0
|
|
|
|
};
|
|
|
|
|
|
|
|
shells[current_shell] = Value::string(path, span);
|
|
|
|
|
|
|
|
stack.add_env_var("NUSHELL_SHELLS".into(), Value::list(shells, span));
|
|
|
|
stack.add_env_var(
|
|
|
|
"NUSHELL_LAST_SHELL".into(),
|
|
|
|
Value::int(last_shell as i64, span),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
///
|
|
|
|
/// Run a command as received from reedline. This is where we are actually
|
|
|
|
/// running a thing!
|
|
|
|
///
|
|
|
|
fn do_run_cmd(
|
|
|
|
s: &str,
|
|
|
|
stack: &mut Stack,
|
|
|
|
engine_state: &mut EngineState,
|
|
|
|
// we pass in the line editor so it can be dropped in the case of a process exit
|
|
|
|
// (in the normal case we don't want to drop it so return it as-is otherwise)
|
|
|
|
line_editor: Reedline,
|
|
|
|
shell_integration: bool,
|
|
|
|
entry_num: usize,
|
|
|
|
) -> Result<Reedline> {
|
|
|
|
trace!("eval source: {}", s);
|
|
|
|
|
|
|
|
let mut cmds = s.split_whitespace();
|
|
|
|
if let Some("exit") = cmds.next() {
|
|
|
|
let mut working_set = StateWorkingSet::new(engine_state);
|
|
|
|
let _ = parse(&mut working_set, None, s.as_bytes(), false);
|
|
|
|
|
|
|
|
if working_set.parse_errors.is_empty() {
|
|
|
|
match cmds.next() {
|
|
|
|
Some(s) => {
|
|
|
|
if let Ok(n) = s.parse::<i32>() {
|
|
|
|
drop(line_editor);
|
|
|
|
std::process::exit(n);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
None => {
|
|
|
|
drop(line_editor);
|
|
|
|
std::process::exit(0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if shell_integration {
|
|
|
|
if let Some(cwd) = stack.get_env_var(engine_state, "PWD") {
|
2024-02-17 19:14:16 +01:00
|
|
|
let path = cwd.coerce_into_string()?;
|
2024-01-31 18:32:19 +01:00
|
|
|
|
|
|
|
// Try to abbreviate string for windows title
|
|
|
|
let maybe_abbrev_path = if let Some(p) = nu_path::home_dir() {
|
|
|
|
path.replace(&p.as_path().display().to_string(), "~")
|
|
|
|
} else {
|
|
|
|
path
|
|
|
|
};
|
|
|
|
let binary_name = s.split_whitespace().next();
|
|
|
|
|
|
|
|
if let Some(binary_name) = binary_name {
|
|
|
|
run_ansi_sequence(&format!("\x1b]2;{maybe_abbrev_path}> {binary_name}\x07"))?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
eval_source(
|
|
|
|
engine_state,
|
|
|
|
stack,
|
|
|
|
s.as_bytes(),
|
|
|
|
&format!("entry #{entry_num}"),
|
|
|
|
PipelineData::empty(),
|
|
|
|
false,
|
|
|
|
);
|
|
|
|
|
|
|
|
Ok(line_editor)
|
|
|
|
}
|
|
|
|
|
|
|
|
///
|
|
|
|
/// Output some things and set environment variables so shells with the right integration
|
|
|
|
/// can have more information about what is going on (after we have run a command)
|
|
|
|
///
|
|
|
|
fn do_shell_integration_finalize_command(
|
|
|
|
hostname: Option<String>,
|
|
|
|
engine_state: &EngineState,
|
|
|
|
stack: &mut Stack,
|
|
|
|
) -> Result<()> {
|
|
|
|
run_ansi_sequence(&get_command_finished_marker(stack, engine_state))?;
|
|
|
|
if let Some(cwd) = stack.get_env_var(engine_state, "PWD") {
|
2024-02-17 19:14:16 +01:00
|
|
|
let path = cwd.coerce_into_string()?;
|
2024-01-31 18:32:19 +01:00
|
|
|
|
|
|
|
// Supported escape sequences of Microsoft's Visual Studio Code (vscode)
|
|
|
|
// https://code.visualstudio.com/docs/terminal/shell-integration#_supported-escape-sequences
|
|
|
|
if stack.get_env_var(engine_state, "TERM_PROGRAM") == Some(Value::test_string("vscode")) {
|
|
|
|
// If we're in vscode, run their specific ansi escape sequence.
|
|
|
|
// This is helpful for ctrl+g to change directories in the terminal.
|
|
|
|
run_ansi_sequence(&format!("\x1b]633;P;Cwd={}\x1b\\", path))?;
|
|
|
|
} else {
|
|
|
|
// Otherwise, communicate the path as OSC 7 (often used for spawning new tabs in the same dir)
|
|
|
|
run_ansi_sequence(&format!(
|
|
|
|
"\x1b]7;file://{}{}{}\x1b\\",
|
|
|
|
percent_encoding::utf8_percent_encode(
|
|
|
|
&hostname.unwrap_or_else(|| "localhost".to_string()),
|
|
|
|
percent_encoding::CONTROLS
|
|
|
|
),
|
|
|
|
if path.starts_with('/') { "" } else { "/" },
|
|
|
|
percent_encoding::utf8_percent_encode(&path, percent_encoding::CONTROLS)
|
|
|
|
))?;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Try to abbreviate string for windows title
|
|
|
|
let maybe_abbrev_path = if let Some(p) = nu_path::home_dir() {
|
|
|
|
path.replace(&p.as_path().display().to_string(), "~")
|
|
|
|
} else {
|
|
|
|
path
|
|
|
|
};
|
|
|
|
|
|
|
|
// Set window title too
|
|
|
|
// https://tldp.org/HOWTO/Xterm-Title-3.html
|
|
|
|
// ESC]0;stringBEL -- Set icon name and window title to string
|
|
|
|
// ESC]1;stringBEL -- Set icon name to string
|
|
|
|
// ESC]2;stringBEL -- Set window title to string
|
|
|
|
run_ansi_sequence(&format!("\x1b]2;{maybe_abbrev_path}\x07"))?;
|
|
|
|
}
|
|
|
|
run_ansi_sequence(RESET_APPLICATION_MODE)?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
///
|
|
|
|
/// Clear the screen and output anything remaining in the EngineState buffer.
|
|
|
|
///
|
|
|
|
fn flush_engine_state_repl_buffer(engine_state: &mut EngineState, line_editor: &mut Reedline) {
|
|
|
|
let mut repl = engine_state.repl_state.lock().expect("repl state mutex");
|
|
|
|
line_editor.run_edit_commands(&[
|
|
|
|
EditCommand::Clear,
|
|
|
|
EditCommand::InsertString(repl.buffer.to_string()),
|
|
|
|
EditCommand::MoveToPosition {
|
|
|
|
position: repl.cursor_pos,
|
|
|
|
select: false,
|
|
|
|
},
|
|
|
|
]);
|
|
|
|
repl.buffer = "".to_string();
|
|
|
|
repl.cursor_pos = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
///
|
|
|
|
/// Setup history management for Reedline
|
|
|
|
///
|
|
|
|
fn setup_history(
|
|
|
|
nushell_path: &str,
|
|
|
|
engine_state: &mut EngineState,
|
|
|
|
line_editor: Reedline,
|
|
|
|
history: HistoryConfig,
|
|
|
|
) -> Result<Reedline> {
|
|
|
|
// Setup history_isolation aka "history per session"
|
|
|
|
let history_session_id = if history.isolation {
|
|
|
|
Reedline::create_history_session_id()
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
|
|
|
|
|
|
|
if let Some(path) = crate::config_files::get_history_path(nushell_path, history.file_format) {
|
|
|
|
return update_line_editor_history(
|
|
|
|
engine_state,
|
|
|
|
path,
|
|
|
|
history,
|
|
|
|
line_editor,
|
|
|
|
history_session_id,
|
|
|
|
);
|
|
|
|
};
|
|
|
|
Ok(line_editor)
|
|
|
|
}
|
|
|
|
|
|
|
|
///
|
|
|
|
/// Setup Reedline keybindingds based on the provided config
|
|
|
|
///
|
|
|
|
fn setup_keybindings(engine_state: &EngineState, line_editor: Reedline) -> Reedline {
|
|
|
|
return match create_keybindings(engine_state.get_config()) {
|
|
|
|
Ok(keybindings) => match keybindings {
|
|
|
|
KeybindingsMode::Emacs(keybindings) => {
|
|
|
|
let edit_mode = Box::new(Emacs::new(keybindings));
|
|
|
|
line_editor.with_edit_mode(edit_mode)
|
|
|
|
}
|
|
|
|
KeybindingsMode::Vi {
|
|
|
|
insert_keybindings,
|
|
|
|
normal_keybindings,
|
|
|
|
} => {
|
|
|
|
let edit_mode = Box::new(Vi::new(insert_keybindings, normal_keybindings));
|
|
|
|
line_editor.with_edit_mode(edit_mode)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
Err(e) => {
|
|
|
|
report_error_new(engine_state, &e);
|
|
|
|
line_editor
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
///
|
|
|
|
/// Make sure that the terminal supports the kitty protocol if the config is asking for it
|
|
|
|
///
|
|
|
|
fn kitty_protocol_healthcheck(engine_state: &EngineState) {
|
|
|
|
if engine_state.get_config().use_kitty_protocol && !reedline::kitty_protocol_available() {
|
|
|
|
warn!("Terminal doesn't support use_kitty_protocol config");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-07 03:16:17 +02:00
|
|
|
fn store_history_id_in_engine(engine_state: &mut EngineState, line_editor: &Reedline) {
|
|
|
|
let session_id = line_editor
|
|
|
|
.get_history_session_id()
|
|
|
|
.map(i64::from)
|
|
|
|
.unwrap_or(0);
|
|
|
|
|
|
|
|
engine_state.history_session_id = session_id;
|
|
|
|
}
|
|
|
|
|
|
|
|
fn update_line_editor_history(
|
|
|
|
engine_state: &mut EngineState,
|
2024-01-17 16:40:59 +01:00
|
|
|
history_path: PathBuf,
|
|
|
|
history: HistoryConfig,
|
2023-07-07 03:16:17 +02:00
|
|
|
line_editor: Reedline,
|
|
|
|
history_session_id: Option<HistorySessionId>,
|
|
|
|
) -> Result<Reedline, ErrReport> {
|
2024-01-17 16:40:59 +01:00
|
|
|
let history: Box<dyn reedline::History> = match history.file_format {
|
2023-07-07 03:16:17 +02:00
|
|
|
HistoryFileFormat::PlainText => Box::new(
|
2024-01-17 16:40:59 +01:00
|
|
|
FileBackedHistory::with_file(history.max_size as usize, history_path)
|
|
|
|
.into_diagnostic()?,
|
2023-07-07 03:16:17 +02:00
|
|
|
),
|
2023-09-18 07:49:26 +02:00
|
|
|
HistoryFileFormat::Sqlite => Box::new(
|
|
|
|
SqliteBackedHistory::with_file(
|
|
|
|
history_path.to_path_buf(),
|
|
|
|
history_session_id,
|
|
|
|
Some(chrono::Utc::now()),
|
|
|
|
)
|
|
|
|
.into_diagnostic()?,
|
|
|
|
),
|
2023-07-07 03:16:17 +02:00
|
|
|
};
|
|
|
|
let line_editor = line_editor
|
|
|
|
.with_history_session_id(history_session_id)
|
|
|
|
.with_history_exclusion_prefix(Some(" ".into()))
|
|
|
|
.with_history(history);
|
|
|
|
|
|
|
|
store_history_id_in_engine(engine_state, &line_editor);
|
|
|
|
|
|
|
|
Ok(line_editor)
|
|
|
|
}
|
|
|
|
|
2024-01-31 18:32:19 +01:00
|
|
|
fn confirm_stdin_is_terminal() -> Result<()> {
|
|
|
|
// Guard against invocation without a connected terminal.
|
|
|
|
// reedline / crossterm event polling will fail without a connected tty
|
|
|
|
if !std::io::stdin().is_terminal() {
|
|
|
|
return Err(std::io::Error::new(
|
|
|
|
std::io::ErrorKind::NotFound,
|
|
|
|
"Nushell launched as a REPL, but STDIN is not a TTY; either launch in a valid terminal or provide arguments to invoke a script!",
|
|
|
|
))
|
|
|
|
.into_diagnostic();
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
2023-11-08 20:31:30 +01:00
|
|
|
fn map_nucursorshape_to_cursorshape(shape: NuCursorShape) -> Option<SetCursorStyle> {
|
2023-01-13 21:37:39 +01:00
|
|
|
match shape {
|
2023-11-08 20:31:30 +01:00
|
|
|
NuCursorShape::Block => Some(SetCursorStyle::SteadyBlock),
|
|
|
|
NuCursorShape::UnderScore => Some(SetCursorStyle::SteadyUnderScore),
|
|
|
|
NuCursorShape::Line => Some(SetCursorStyle::SteadyBar),
|
|
|
|
NuCursorShape::BlinkBlock => Some(SetCursorStyle::BlinkingBlock),
|
|
|
|
NuCursorShape::BlinkUnderScore => Some(SetCursorStyle::BlinkingUnderScore),
|
|
|
|
NuCursorShape::BlinkLine => Some(SetCursorStyle::BlinkingBar),
|
|
|
|
NuCursorShape::Inherit => None,
|
2023-01-13 21:37:39 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-18 05:20:31 +01:00
|
|
|
fn get_command_finished_marker(stack: &Stack, engine_state: &EngineState) -> String {
|
2022-07-20 22:03:29 +02:00
|
|
|
let exit_code = stack
|
|
|
|
.get_env_var(engine_state, "LAST_EXIT_CODE")
|
|
|
|
.and_then(|e| e.as_i64().ok());
|
|
|
|
|
|
|
|
format!("\x1b]133;D;{}\x1b\\", exit_code.unwrap_or(0))
|
|
|
|
}
|
|
|
|
|
2022-07-10 12:45:46 +02:00
|
|
|
fn run_ansi_sequence(seq: &str) -> Result<(), ShellError> {
|
2023-12-07 00:40:03 +01:00
|
|
|
io::stdout()
|
|
|
|
.write_all(seq.as_bytes())
|
|
|
|
.map_err(|e| ShellError::GenericError {
|
|
|
|
error: "Error writing ansi sequence".into(),
|
|
|
|
msg: e.to_string(),
|
|
|
|
span: Some(Span::unknown()),
|
|
|
|
help: None,
|
|
|
|
inner: vec![],
|
|
|
|
})?;
|
|
|
|
io::stdout().flush().map_err(|e| ShellError::GenericError {
|
|
|
|
error: "Error flushing stdio".into(),
|
|
|
|
msg: e.to_string(),
|
|
|
|
span: Some(Span::unknown()),
|
|
|
|
help: None,
|
|
|
|
inner: vec![],
|
2022-07-10 12:45:46 +02:00
|
|
|
})
|
|
|
|
}
|
2022-07-16 04:01:38 +02:00
|
|
|
|
2022-12-17 19:30:04 +01:00
|
|
|
// Absolute paths with a drive letter, like 'C:', 'D:\', 'E:\foo'
|
|
|
|
#[cfg(windows)]
|
|
|
|
static DRIVE_PATH_REGEX: once_cell::sync::Lazy<fancy_regex::Regex> =
|
|
|
|
once_cell::sync::Lazy::new(|| {
|
|
|
|
fancy_regex::Regex::new(r"^[a-zA-Z]:[/\\]?").expect("Internal error: regex creation")
|
|
|
|
});
|
2022-07-16 04:01:38 +02:00
|
|
|
|
|
|
|
// A best-effort "does this string look kinda like a path?" function to determine whether to auto-cd
|
|
|
|
fn looks_like_path(orig: &str) -> bool {
|
|
|
|
#[cfg(windows)]
|
|
|
|
{
|
2022-08-04 21:51:02 +02:00
|
|
|
if DRIVE_PATH_REGEX.is_match(orig).unwrap_or(false) {
|
2022-07-16 04:01:38 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
orig.starts_with('.')
|
|
|
|
|| orig.starts_with('~')
|
|
|
|
|| orig.starts_with('/')
|
|
|
|
|| orig.starts_with('\\')
|
2023-10-02 21:14:02 +02:00
|
|
|
|| orig.ends_with(std::path::MAIN_SEPARATOR)
|
2022-07-16 04:01:38 +02:00
|
|
|
}
|
|
|
|
|
2023-10-02 21:14:02 +02:00
|
|
|
#[cfg(windows)]
|
2022-07-16 04:01:38 +02:00
|
|
|
#[test]
|
|
|
|
fn looks_like_path_windows_drive_path_works() {
|
2023-10-02 21:14:02 +02:00
|
|
|
assert!(looks_like_path("C:"));
|
|
|
|
assert!(looks_like_path("D:\\"));
|
|
|
|
assert!(looks_like_path("E:/"));
|
|
|
|
assert!(looks_like_path("F:\\some_dir"));
|
|
|
|
assert!(looks_like_path("G:/some_dir"));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(windows)]
|
|
|
|
#[test]
|
|
|
|
fn trailing_slash_looks_like_path() {
|
|
|
|
assert!(looks_like_path("foo\\"))
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(not(windows))]
|
|
|
|
#[test]
|
|
|
|
fn trailing_slash_looks_like_path() {
|
|
|
|
assert!(looks_like_path("foo/"))
|
2022-07-16 04:01:38 +02:00
|
|
|
}
|
2023-07-07 03:16:17 +02:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn are_session_ids_in_sync() {
|
|
|
|
let engine_state = &mut EngineState::new();
|
2024-01-17 16:40:59 +01:00
|
|
|
let history = engine_state.history_config().unwrap();
|
|
|
|
let history_path =
|
|
|
|
crate::config_files::get_history_path("nushell", history.file_format).unwrap();
|
2023-07-07 03:16:17 +02:00
|
|
|
let line_editor = reedline::Reedline::create();
|
|
|
|
let history_session_id = reedline::Reedline::create_history_session_id();
|
2024-01-17 16:40:59 +01:00
|
|
|
let line_editor = update_line_editor_history(
|
|
|
|
engine_state,
|
|
|
|
history_path,
|
|
|
|
history,
|
|
|
|
line_editor,
|
|
|
|
history_session_id,
|
|
|
|
);
|
2023-07-07 03:16:17 +02:00
|
|
|
assert_eq!(
|
|
|
|
i64::from(line_editor.unwrap().get_history_session_id().unwrap()),
|
|
|
|
engine_state.history_session_id
|
|
|
|
);
|
|
|
|
}
|