mirror of
https://github.com/fish-shell/fish-shell.git
synced 2024-12-19 21:43:41 +08:00
640 lines
20 KiB
Rust
640 lines
20 KiB
Rust
use crate::common::ScopeGuard;
|
|
use crate::env::EnvMode;
|
|
use crate::future_feature_flags::{self, FeatureFlag};
|
|
use crate::tests::prelude::*;
|
|
use crate::wchar::prelude::*;
|
|
use crate::{
|
|
env::EnvStack,
|
|
highlight::{highlight_shell, is_potential_path, HighlightRole, HighlightSpec, PathFlags},
|
|
operation_context::{OperationContext, EXPANSION_LIMIT_BACKGROUND, EXPANSION_LIMIT_DEFAULT},
|
|
};
|
|
use libc::PATH_MAX;
|
|
|
|
// Helper to return a string whose length greatly exceeds PATH_MAX.
|
|
fn get_overlong_path() -> String {
|
|
let path_max = usize::try_from(PATH_MAX).unwrap();
|
|
let mut longpath = String::with_capacity(path_max * 2 + 10);
|
|
while longpath.len() <= path_max * 2 {
|
|
longpath += "/overlong";
|
|
}
|
|
longpath
|
|
}
|
|
|
|
#[test]
|
|
#[serial]
|
|
fn test_is_potential_path() {
|
|
let _cleanup = test_init();
|
|
// Directories
|
|
std::fs::create_dir_all("test/is_potential_path_test/alpha/").unwrap();
|
|
std::fs::create_dir_all("test/is_potential_path_test/beta/").unwrap();
|
|
|
|
// Files
|
|
std::fs::write("test/is_potential_path_test/aardvark", []).unwrap();
|
|
std::fs::write("test/is_potential_path_test/gamma", []).unwrap();
|
|
|
|
let wd = L!("test/is_potential_path_test/").to_owned();
|
|
let wds = [L!(".").to_owned(), wd];
|
|
|
|
let vars = EnvStack::new();
|
|
let ctx = OperationContext::background(&vars, EXPANSION_LIMIT_DEFAULT);
|
|
|
|
assert!(is_potential_path(
|
|
L!("al"),
|
|
true,
|
|
&wds[..],
|
|
&ctx,
|
|
PathFlags::PATH_REQUIRE_DIR
|
|
));
|
|
|
|
assert!(is_potential_path(
|
|
L!("alpha/"),
|
|
true,
|
|
&wds[..],
|
|
&ctx,
|
|
PathFlags::PATH_REQUIRE_DIR
|
|
));
|
|
assert!(is_potential_path(
|
|
L!("aard"),
|
|
true,
|
|
&wds[..],
|
|
&ctx,
|
|
PathFlags::empty()
|
|
));
|
|
assert!(!is_potential_path(
|
|
L!("aard"),
|
|
false,
|
|
&wds[..],
|
|
&ctx,
|
|
PathFlags::empty()
|
|
));
|
|
assert!(!is_potential_path(
|
|
L!("alp/"),
|
|
true,
|
|
&wds[..],
|
|
&ctx,
|
|
PathFlags::PATH_REQUIRE_DIR | PathFlags::PATH_FOR_CD
|
|
));
|
|
|
|
assert!(!is_potential_path(
|
|
L!("balpha/"),
|
|
true,
|
|
&wds[..],
|
|
&ctx,
|
|
PathFlags::PATH_REQUIRE_DIR
|
|
));
|
|
assert!(!is_potential_path(
|
|
L!("aard"),
|
|
true,
|
|
&wds[..],
|
|
&ctx,
|
|
PathFlags::PATH_REQUIRE_DIR
|
|
));
|
|
assert!(!is_potential_path(
|
|
L!("aarde"),
|
|
true,
|
|
&wds[..],
|
|
&ctx,
|
|
PathFlags::PATH_REQUIRE_DIR
|
|
));
|
|
assert!(!is_potential_path(
|
|
L!("aarde"),
|
|
true,
|
|
&wds[..],
|
|
&ctx,
|
|
PathFlags::empty()
|
|
));
|
|
|
|
assert!(is_potential_path(
|
|
L!("test/is_potential_path_test/aardvark"),
|
|
true,
|
|
&wds[..],
|
|
&ctx,
|
|
PathFlags::empty()
|
|
));
|
|
assert!(is_potential_path(
|
|
L!("test/is_potential_path_test/al"),
|
|
true,
|
|
&wds[..],
|
|
&ctx,
|
|
PathFlags::PATH_REQUIRE_DIR
|
|
));
|
|
assert!(is_potential_path(
|
|
L!("test/is_potential_path_test/aardv"),
|
|
true,
|
|
&wds[..],
|
|
&ctx,
|
|
PathFlags::empty()
|
|
));
|
|
|
|
assert!(!is_potential_path(
|
|
L!("test/is_potential_path_test/aardvark"),
|
|
true,
|
|
&wds[..],
|
|
&ctx,
|
|
PathFlags::PATH_REQUIRE_DIR
|
|
));
|
|
assert!(!is_potential_path(
|
|
L!("test/is_potential_path_test/al/"),
|
|
true,
|
|
&wds[..],
|
|
&ctx,
|
|
PathFlags::empty()
|
|
));
|
|
assert!(!is_potential_path(
|
|
L!("test/is_potential_path_test/ar"),
|
|
true,
|
|
&wds[..],
|
|
&ctx,
|
|
PathFlags::empty()
|
|
));
|
|
assert!(is_potential_path(
|
|
L!("/usr"),
|
|
true,
|
|
&wds[..],
|
|
&ctx,
|
|
PathFlags::PATH_REQUIRE_DIR
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
#[serial]
|
|
fn test_highlighting() {
|
|
let _cleanup = test_init();
|
|
let parser = TestParser::new();
|
|
// Testing syntax highlighting
|
|
parser.pushd("test/fish_highlight_test/");
|
|
let _popd = ScopeGuard::new((), |_| parser.popd());
|
|
std::fs::create_dir_all("dir").unwrap();
|
|
std::fs::create_dir_all("cdpath-entry/dir-in-cdpath").unwrap();
|
|
std::fs::write("foo", []).unwrap();
|
|
std::fs::write("bar", []).unwrap();
|
|
|
|
// Here are the components of our source and the colors we expect those to be.
|
|
#[derive(Debug)]
|
|
struct HighlightComponent<'a> {
|
|
text: &'a str,
|
|
color: HighlightSpec,
|
|
nospace: bool,
|
|
}
|
|
|
|
macro_rules! component {
|
|
( ( $text:expr, $color:expr) ) => {
|
|
HighlightComponent {
|
|
text: $text,
|
|
color: $color,
|
|
nospace: false,
|
|
}
|
|
};
|
|
( ( $text:literal, $color:expr, ns ) ) => {
|
|
HighlightComponent {
|
|
text: $text,
|
|
color: $color,
|
|
nospace: true,
|
|
}
|
|
};
|
|
}
|
|
|
|
macro_rules! validate {
|
|
( $($comp:tt),* $(,)? ) => {
|
|
let components = [
|
|
$(
|
|
component!($comp),
|
|
)*
|
|
];
|
|
let vars = parser.vars();
|
|
// Generate the text.
|
|
let mut text = WString::new();
|
|
let mut expected_colors = vec![];
|
|
for comp in &components {
|
|
if !text.is_empty() && !comp.nospace {
|
|
text.push(' ');
|
|
expected_colors.push(HighlightSpec::new());
|
|
}
|
|
text.push_str(comp.text);
|
|
expected_colors.resize(text.len(), comp.color);
|
|
}
|
|
assert_eq!(text.len(), expected_colors.len());
|
|
|
|
let mut colors = vec![];
|
|
highlight_shell(
|
|
&text,
|
|
&mut colors,
|
|
&OperationContext::background(vars, EXPANSION_LIMIT_BACKGROUND),
|
|
true, /* io_ok */
|
|
Some(text.len()),
|
|
);
|
|
assert_eq!(colors.len(), expected_colors.len());
|
|
|
|
for (i, c) in text.chars().enumerate() {
|
|
// Hackish space handling. We don't care about the colors in spaces.
|
|
if c == ' ' {
|
|
continue;
|
|
}
|
|
|
|
assert_eq!(colors[i], expected_colors[i], "Failed at position {i}, char {c}");
|
|
}
|
|
};
|
|
}
|
|
|
|
let mut param_valid_path = HighlightSpec::with_fg(HighlightRole::param);
|
|
param_valid_path.valid_path = true;
|
|
|
|
let saved_flag = future_feature_flags::test(FeatureFlag::ampersand_nobg_in_token);
|
|
future_feature_flags::set(FeatureFlag::ampersand_nobg_in_token, true);
|
|
let _restore_saved_flag = ScopeGuard::new((), |_| {
|
|
future_feature_flags::set(FeatureFlag::ampersand_nobg_in_token, saved_flag);
|
|
});
|
|
|
|
let fg = HighlightSpec::with_fg;
|
|
|
|
// Verify variables and wildcards in commands using /bin/cat.
|
|
let vars = parser.vars();
|
|
vars.set_one(
|
|
L!("CDPATH"),
|
|
EnvMode::LOCAL,
|
|
L!("./cdpath-entry").to_owned(),
|
|
);
|
|
|
|
vars.set_one(
|
|
L!("VARIABLE_IN_COMMAND"),
|
|
EnvMode::LOCAL,
|
|
L!("a").to_owned(),
|
|
);
|
|
vars.set_one(
|
|
L!("VARIABLE_IN_COMMAND2"),
|
|
EnvMode::LOCAL,
|
|
L!("at").to_owned(),
|
|
);
|
|
|
|
let _cleanup = ScopeGuard::new((), |_| {
|
|
vars.remove(L!("VARIABLE_IN_COMMAND"), EnvMode::default());
|
|
vars.remove(L!("VARIABLE_IN_COMMAND2"), EnvMode::default());
|
|
});
|
|
|
|
validate!(
|
|
("echo", fg(HighlightRole::command)),
|
|
("./foo", param_valid_path),
|
|
("&", fg(HighlightRole::statement_terminator)),
|
|
);
|
|
|
|
validate!(
|
|
("command", fg(HighlightRole::keyword)),
|
|
("echo", fg(HighlightRole::command)),
|
|
("abc", fg(HighlightRole::param)),
|
|
("foo", param_valid_path),
|
|
("&", fg(HighlightRole::statement_terminator)),
|
|
);
|
|
|
|
validate!(
|
|
("echo", fg(HighlightRole::command)),
|
|
("foo&bar", fg(HighlightRole::param)),
|
|
("foo", fg(HighlightRole::param), ns),
|
|
("&", fg(HighlightRole::statement_terminator)),
|
|
("echo", fg(HighlightRole::command)),
|
|
("&>", fg(HighlightRole::redirection)),
|
|
);
|
|
|
|
validate!(
|
|
("if command", fg(HighlightRole::keyword)),
|
|
("ls", fg(HighlightRole::command)),
|
|
("; ", fg(HighlightRole::statement_terminator)),
|
|
("echo", fg(HighlightRole::command)),
|
|
("abc", fg(HighlightRole::param)),
|
|
("; ", fg(HighlightRole::statement_terminator)),
|
|
("/bin/definitely_not_a_command", fg(HighlightRole::error)),
|
|
("; ", fg(HighlightRole::statement_terminator)),
|
|
("end", fg(HighlightRole::keyword)),
|
|
);
|
|
|
|
// Verify that cd shows errors for non-directories.
|
|
validate!(
|
|
("cd", fg(HighlightRole::command)),
|
|
("dir", param_valid_path),
|
|
);
|
|
|
|
validate!(
|
|
("cd", fg(HighlightRole::command)),
|
|
("foo", fg(HighlightRole::error)),
|
|
);
|
|
|
|
validate!(
|
|
("cd", fg(HighlightRole::command)),
|
|
("--help", fg(HighlightRole::option)),
|
|
("-h", fg(HighlightRole::option)),
|
|
("definitely_not_a_directory", fg(HighlightRole::error)),
|
|
);
|
|
|
|
validate!(
|
|
("cd", fg(HighlightRole::command)),
|
|
("dir-in-cdpath", param_valid_path),
|
|
);
|
|
|
|
// Command substitutions.
|
|
validate!(
|
|
("echo", fg(HighlightRole::command)),
|
|
("param1", fg(HighlightRole::param)),
|
|
("-l", fg(HighlightRole::option)),
|
|
("--", fg(HighlightRole::option)),
|
|
("-l", fg(HighlightRole::param)),
|
|
("(", fg(HighlightRole::operat)),
|
|
("ls", fg(HighlightRole::command)),
|
|
("-l", fg(HighlightRole::option)),
|
|
("--", fg(HighlightRole::option)),
|
|
("-l", fg(HighlightRole::param)),
|
|
("param2", fg(HighlightRole::param)),
|
|
(")", fg(HighlightRole::operat)),
|
|
("|", fg(HighlightRole::statement_terminator)),
|
|
("cat", fg(HighlightRole::command)),
|
|
);
|
|
validate!(
|
|
("true", fg(HighlightRole::command)),
|
|
("$(", fg(HighlightRole::operat)),
|
|
("true", fg(HighlightRole::command)),
|
|
(")", fg(HighlightRole::operat)),
|
|
);
|
|
validate!(
|
|
("true", fg(HighlightRole::command)),
|
|
("\"before", fg(HighlightRole::quote)),
|
|
("$(", fg(HighlightRole::operat)),
|
|
("true", fg(HighlightRole::command)),
|
|
("param1", fg(HighlightRole::param)),
|
|
(")", fg(HighlightRole::operat)),
|
|
("after\"", fg(HighlightRole::quote)),
|
|
("param2", fg(HighlightRole::param)),
|
|
);
|
|
validate!(
|
|
("true", fg(HighlightRole::command)),
|
|
("\"", fg(HighlightRole::error)),
|
|
("unclosed quote", fg(HighlightRole::quote)),
|
|
("$(", fg(HighlightRole::operat)),
|
|
("true", fg(HighlightRole::command)),
|
|
(")", fg(HighlightRole::operat)),
|
|
);
|
|
|
|
// Redirections substitutions.
|
|
validate!(
|
|
("echo", fg(HighlightRole::command)),
|
|
("param1", fg(HighlightRole::param)),
|
|
// Input redirection.
|
|
("<", fg(HighlightRole::redirection)),
|
|
("/bin/echo", fg(HighlightRole::redirection)),
|
|
// Output redirection to a valid fd.
|
|
("1>&2", fg(HighlightRole::redirection)),
|
|
// Output redirection to an invalid fd.
|
|
("2>&", fg(HighlightRole::redirection)),
|
|
("LO", fg(HighlightRole::error)),
|
|
// Just a param, not a redirection.
|
|
("test/blah", fg(HighlightRole::param)),
|
|
// Input redirection from directory.
|
|
("<", fg(HighlightRole::redirection)),
|
|
("test/", fg(HighlightRole::error)),
|
|
// Output redirection to an invalid path.
|
|
("3>", fg(HighlightRole::redirection)),
|
|
("/not/a/valid/path/nope", fg(HighlightRole::error)),
|
|
// Output redirection to directory.
|
|
("3>", fg(HighlightRole::redirection)),
|
|
("test/nope/", fg(HighlightRole::error)),
|
|
// Redirections to overflow fd.
|
|
("99999999999999999999>&2", fg(HighlightRole::error)),
|
|
("2>&", fg(HighlightRole::redirection)),
|
|
("99999999999999999999", fg(HighlightRole::error)),
|
|
// Output redirection containing a command substitution.
|
|
("4>", fg(HighlightRole::redirection)),
|
|
("(", fg(HighlightRole::operat)),
|
|
("echo", fg(HighlightRole::command)),
|
|
("test/somewhere", fg(HighlightRole::param)),
|
|
(")", fg(HighlightRole::operat)),
|
|
// Just another param.
|
|
("param2", fg(HighlightRole::param)),
|
|
);
|
|
|
|
validate!(
|
|
("for", fg(HighlightRole::keyword)),
|
|
("x", fg(HighlightRole::param)),
|
|
("in", fg(HighlightRole::keyword)),
|
|
("set-by-for-1", fg(HighlightRole::param)),
|
|
("set-by-for-2", fg(HighlightRole::param)),
|
|
(";", fg(HighlightRole::statement_terminator)),
|
|
("echo", fg(HighlightRole::command)),
|
|
(">", fg(HighlightRole::redirection)),
|
|
("$x", fg(HighlightRole::redirection)),
|
|
(";", fg(HighlightRole::statement_terminator)),
|
|
("end", fg(HighlightRole::keyword)),
|
|
);
|
|
|
|
validate!(
|
|
("set", fg(HighlightRole::command)),
|
|
("x", fg(HighlightRole::param)),
|
|
("set-by-set", fg(HighlightRole::param)),
|
|
(";", fg(HighlightRole::statement_terminator)),
|
|
("echo", fg(HighlightRole::command)),
|
|
(">", fg(HighlightRole::redirection)),
|
|
("$x", fg(HighlightRole::redirection)),
|
|
("2>", fg(HighlightRole::redirection)),
|
|
("$totally_not_x", fg(HighlightRole::error)),
|
|
("<", fg(HighlightRole::redirection)),
|
|
("$x_but_its_an_impostor", fg(HighlightRole::error)),
|
|
);
|
|
|
|
validate!(
|
|
("x", fg(HighlightRole::param), ns),
|
|
("=", fg(HighlightRole::operat), ns),
|
|
("set-by-variable-override", fg(HighlightRole::param), ns),
|
|
("echo", fg(HighlightRole::command)),
|
|
(">", fg(HighlightRole::redirection)),
|
|
("$x", fg(HighlightRole::redirection)),
|
|
);
|
|
|
|
validate!(
|
|
("end", fg(HighlightRole::error)),
|
|
(";", fg(HighlightRole::statement_terminator)),
|
|
("if", fg(HighlightRole::keyword)),
|
|
("end", fg(HighlightRole::error)),
|
|
);
|
|
|
|
validate!(
|
|
("echo", fg(HighlightRole::command)),
|
|
("'", fg(HighlightRole::error)),
|
|
("single_quote", fg(HighlightRole::quote)),
|
|
("$stuff", fg(HighlightRole::quote)),
|
|
);
|
|
|
|
validate!(
|
|
("echo", fg(HighlightRole::command)),
|
|
("\"", fg(HighlightRole::error)),
|
|
("double_quote", fg(HighlightRole::quote)),
|
|
("$stuff", fg(HighlightRole::operat)),
|
|
);
|
|
|
|
validate!(
|
|
("echo", fg(HighlightRole::command)),
|
|
("$foo", fg(HighlightRole::operat)),
|
|
("\"", fg(HighlightRole::quote)),
|
|
("$bar", fg(HighlightRole::operat)),
|
|
("\"", fg(HighlightRole::quote)),
|
|
("$baz[", fg(HighlightRole::operat)),
|
|
("1 2..3", fg(HighlightRole::param)),
|
|
("]", fg(HighlightRole::operat)),
|
|
);
|
|
|
|
validate!(
|
|
("for", fg(HighlightRole::keyword)),
|
|
("i", fg(HighlightRole::param)),
|
|
("in", fg(HighlightRole::keyword)),
|
|
("1 2 3", fg(HighlightRole::param)),
|
|
(";", fg(HighlightRole::statement_terminator)),
|
|
("end", fg(HighlightRole::keyword)),
|
|
);
|
|
|
|
validate!(
|
|
("echo", fg(HighlightRole::command)),
|
|
("$$foo[", fg(HighlightRole::operat)),
|
|
("1", fg(HighlightRole::param)),
|
|
("][", fg(HighlightRole::operat)),
|
|
("2", fg(HighlightRole::param)),
|
|
("]", fg(HighlightRole::operat)),
|
|
("[3]", fg(HighlightRole::param)), // two dollar signs, so last one is not an expansion
|
|
);
|
|
|
|
validate!(
|
|
("cat", fg(HighlightRole::command)),
|
|
("/dev/null", param_valid_path),
|
|
("|", fg(HighlightRole::statement_terminator)),
|
|
// This is bogus, but we used to use "less" here and that doesn't have to be installed.
|
|
("cat", fg(HighlightRole::command)),
|
|
("2>", fg(HighlightRole::redirection)),
|
|
);
|
|
|
|
// Highlight path-prefixes only at the cursor.
|
|
validate!(
|
|
("cat", fg(HighlightRole::command)),
|
|
("/dev/nu", fg(HighlightRole::param)),
|
|
("/dev/nu", param_valid_path),
|
|
);
|
|
|
|
validate!(
|
|
("if", fg(HighlightRole::keyword)),
|
|
("true", fg(HighlightRole::command)),
|
|
("&&", fg(HighlightRole::operat)),
|
|
("false", fg(HighlightRole::command)),
|
|
(";", fg(HighlightRole::statement_terminator)),
|
|
("or", fg(HighlightRole::operat)),
|
|
("false", fg(HighlightRole::command)),
|
|
("||", fg(HighlightRole::operat)),
|
|
("true", fg(HighlightRole::command)),
|
|
(";", fg(HighlightRole::statement_terminator)),
|
|
("and", fg(HighlightRole::operat)),
|
|
("not", fg(HighlightRole::operat)),
|
|
("!", fg(HighlightRole::operat)),
|
|
("true", fg(HighlightRole::command)),
|
|
(";", fg(HighlightRole::statement_terminator)),
|
|
("end", fg(HighlightRole::keyword)),
|
|
);
|
|
|
|
validate!(
|
|
("echo", fg(HighlightRole::command)),
|
|
("%self", fg(HighlightRole::operat)),
|
|
("not%self", fg(HighlightRole::param)),
|
|
("self%not", fg(HighlightRole::param)),
|
|
);
|
|
|
|
validate!(
|
|
("false", fg(HighlightRole::command)),
|
|
("&|", fg(HighlightRole::statement_terminator)),
|
|
("true", fg(HighlightRole::command)),
|
|
);
|
|
|
|
validate!(
|
|
("HOME", fg(HighlightRole::param)),
|
|
("=", fg(HighlightRole::operat), ns),
|
|
(".", fg(HighlightRole::param), ns),
|
|
("VAR1", fg(HighlightRole::param)),
|
|
("=", fg(HighlightRole::operat), ns),
|
|
("VAL1", fg(HighlightRole::param), ns),
|
|
("VAR", fg(HighlightRole::param)),
|
|
("=", fg(HighlightRole::operat), ns),
|
|
("false", fg(HighlightRole::command)),
|
|
("|&", fg(HighlightRole::error)),
|
|
("true", fg(HighlightRole::command)),
|
|
("stuff", fg(HighlightRole::param)),
|
|
);
|
|
|
|
validate!(
|
|
("echo", fg(HighlightRole::command)), // (
|
|
(")", fg(HighlightRole::error)),
|
|
);
|
|
|
|
validate!(
|
|
("echo", fg(HighlightRole::command)),
|
|
("stuff", fg(HighlightRole::param)),
|
|
("# comment", fg(HighlightRole::comment)),
|
|
);
|
|
|
|
validate!(
|
|
("echo", fg(HighlightRole::command)),
|
|
("--", fg(HighlightRole::option)),
|
|
("-s", fg(HighlightRole::param)),
|
|
);
|
|
|
|
// Overlong paths don't crash (#7837).
|
|
let overlong = get_overlong_path();
|
|
validate!(
|
|
("touch", fg(HighlightRole::command)),
|
|
(&overlong, fg(HighlightRole::param)),
|
|
);
|
|
|
|
validate!(
|
|
("a", fg(HighlightRole::param)),
|
|
("=", fg(HighlightRole::operat), ns),
|
|
);
|
|
|
|
// Highlighting works across escaped line breaks (#8444).
|
|
validate!(
|
|
("echo", fg(HighlightRole::command)),
|
|
("$FISH_\\\n", fg(HighlightRole::operat)),
|
|
("VERSION", fg(HighlightRole::operat), ns),
|
|
);
|
|
|
|
validate!(
|
|
("/bin/ca", fg(HighlightRole::command), ns),
|
|
("*", fg(HighlightRole::operat), ns)
|
|
);
|
|
|
|
validate!(
|
|
("/bin/c", fg(HighlightRole::command), ns),
|
|
("*", fg(HighlightRole::operat), ns)
|
|
);
|
|
|
|
validate!(
|
|
("/bin/c", fg(HighlightRole::command), ns),
|
|
("{$VARIABLE_IN_COMMAND}", fg(HighlightRole::operat), ns),
|
|
("*", fg(HighlightRole::operat), ns)
|
|
);
|
|
|
|
validate!(
|
|
("/bin/c", fg(HighlightRole::command), ns),
|
|
("$VARIABLE_IN_COMMAND2", fg(HighlightRole::operat), ns)
|
|
);
|
|
|
|
validate!(("$EMPTY_VARIABLE", fg(HighlightRole::error)));
|
|
validate!(("\"$EMPTY_VARIABLE\"", fg(HighlightRole::error)));
|
|
|
|
validate!(
|
|
("echo", fg(HighlightRole::command)),
|
|
("\\UFDFD", fg(HighlightRole::escape)),
|
|
);
|
|
validate!(
|
|
("echo", fg(HighlightRole::command)),
|
|
("\\U10FFFF", fg(HighlightRole::escape)),
|
|
);
|
|
validate!(
|
|
("echo", fg(HighlightRole::command)),
|
|
("\\U110000", fg(HighlightRole::error)),
|
|
);
|
|
|
|
validate!(
|
|
(">", fg(HighlightRole::error)),
|
|
("echo", fg(HighlightRole::error)),
|
|
);
|
|
}
|