mirror of
https://github.com/nushell/nushell.git
synced 2025-05-08 03:54:27 +02:00
Improve case insensitivity consistency (#10884)
# Description Add an extension trait `IgnoreCaseExt` to nu_utils which adds some case insensitivity helpers, and use them throughout nu to improve the handling of case insensitivity. Proper case folding is done via unicase, which is already a dependency via mime_guess from nu-command. In actuality a lot of code still does `to_lowercase`, because unicase only provides immediate comparison and doesn't expose a `to_folded_case` yet. And since we do a lot of `contains`/`starts_with`/`ends_with`, it's not sufficient to just have `eq_ignore_case`. But if we get access in the future, this makes us ready to use it with a change in one place. Plus, it's clearer what the purpose is at the call site to call `to_folded_case` instead of `to_lowercase` if it's exclusively for the purpose of case insensitive comparison, even if it just does `to_lowercase` still. # User-Facing Changes - Some commands that were supposed to be case insensitive remained only insensitive to ASCII case (a-z), and now are case insensitive w.r.t. non-ASCII characters as well. # Tests + Formatting - 🟢 `toolkit fmt` - 🟢 `toolkit clippy` - 🟢 `toolkit test` - 🟢 `toolkit test stdlib` --------- Co-authored-by: Stefan Holderbach <sholderbach@users.noreply.github.com>
This commit is contained in:
parent
aed4b626b8
commit
0f600bc3f5
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -3141,6 +3141,7 @@ dependencies = [
|
|||||||
"num-format",
|
"num-format",
|
||||||
"strip-ansi-escapes",
|
"strip-ansi-escapes",
|
||||||
"sys-locale",
|
"sys-locale",
|
||||||
|
"unicase",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -5,6 +5,7 @@ use nu_protocol::{
|
|||||||
engine::{EngineState, Stack, StateWorkingSet},
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
PipelineData, Span, Type, Value,
|
PipelineData, Span, Type, Value,
|
||||||
};
|
};
|
||||||
|
use nu_utils::IgnoreCaseExt;
|
||||||
use reedline::Suggestion;
|
use reedline::Suggestion;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@ -153,8 +154,8 @@ fn filter(prefix: &[u8], items: Vec<Suggestion>, options: &CompletionOptions) ->
|
|||||||
(true, true) => it.value.as_bytes().starts_with(prefix),
|
(true, true) => it.value.as_bytes().starts_with(prefix),
|
||||||
(true, false) => it.value.contains(std::str::from_utf8(prefix).unwrap_or("")),
|
(true, false) => it.value.contains(std::str::from_utf8(prefix).unwrap_or("")),
|
||||||
(false, positional) => {
|
(false, positional) => {
|
||||||
let value = it.value.to_lowercase();
|
let value = it.value.to_folded_case();
|
||||||
let prefix = std::str::from_utf8(prefix).unwrap_or("").to_lowercase();
|
let prefix = std::str::from_utf8(prefix).unwrap_or("").to_folded_case();
|
||||||
if positional {
|
if positional {
|
||||||
value.starts_with(&prefix)
|
value.starts_with(&prefix)
|
||||||
} else {
|
} else {
|
||||||
|
@ -6,6 +6,7 @@ use nu_protocol::{
|
|||||||
engine::{EngineState, StateWorkingSet},
|
engine::{EngineState, StateWorkingSet},
|
||||||
levenshtein_distance, Span,
|
levenshtein_distance, Span,
|
||||||
};
|
};
|
||||||
|
use nu_utils::IgnoreCaseExt;
|
||||||
use reedline::Suggestion;
|
use reedline::Suggestion;
|
||||||
use std::path::{Path, MAIN_SEPARATOR as SEP};
|
use std::path::{Path, MAIN_SEPARATOR as SEP};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@ -125,7 +126,7 @@ pub fn matches(partial: &str, from: &str, options: &CompletionOptions) -> bool {
|
|||||||
if !options.case_sensitive {
|
if !options.case_sensitive {
|
||||||
return options
|
return options
|
||||||
.match_algorithm
|
.match_algorithm
|
||||||
.matches_str(&from.to_ascii_lowercase(), &partial.to_ascii_lowercase());
|
.matches_str(&from.to_folded_case(), &partial.to_folded_case());
|
||||||
}
|
}
|
||||||
|
|
||||||
options.match_algorithm.matches_str(from, partial)
|
options.match_algorithm.matches_str(from, partial)
|
||||||
|
@ -44,9 +44,7 @@ impl Completer for VariableCompletion {
|
|||||||
) -> Vec<Suggestion> {
|
) -> Vec<Suggestion> {
|
||||||
let mut output = vec![];
|
let mut output = vec![];
|
||||||
let builtins = ["$nu", "$in", "$env"];
|
let builtins = ["$nu", "$in", "$env"];
|
||||||
let var_str = std::str::from_utf8(&self.var_context.0)
|
let var_str = std::str::from_utf8(&self.var_context.0).unwrap_or("");
|
||||||
.unwrap_or("")
|
|
||||||
.to_lowercase();
|
|
||||||
let var_id = working_set.find_variable(&self.var_context.0);
|
let var_id = working_set.find_variable(&self.var_context.0);
|
||||||
let current_span = reedline::Span {
|
let current_span = reedline::Span {
|
||||||
start: span.start - offset,
|
start: span.start - offset,
|
||||||
@ -57,7 +55,7 @@ impl Completer for VariableCompletion {
|
|||||||
// Completions for the given variable
|
// Completions for the given variable
|
||||||
if !var_str.is_empty() {
|
if !var_str.is_empty() {
|
||||||
// Completion for $env.<tab>
|
// Completion for $env.<tab>
|
||||||
if var_str.as_str() == "$env" {
|
if var_str == "$env" {
|
||||||
let env_vars = self.stack.get_env_vars(&self.engine_state);
|
let env_vars = self.stack.get_env_vars(&self.engine_state);
|
||||||
|
|
||||||
// Return nested values
|
// Return nested values
|
||||||
@ -109,7 +107,7 @@ impl Completer for VariableCompletion {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Completions for $nu.<tab>
|
// Completions for $nu.<tab>
|
||||||
if var_str.as_str() == "$nu" {
|
if var_str == "$nu" {
|
||||||
// Eval nu var
|
// Eval nu var
|
||||||
if let Ok(nuval) = eval_variable(
|
if let Ok(nuval) = eval_variable(
|
||||||
&self.engine_state,
|
&self.engine_state,
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use nu_engine::documentation::get_flags_section;
|
use nu_engine::documentation::get_flags_section;
|
||||||
use nu_protocol::{engine::EngineState, levenshtein_distance};
|
use nu_protocol::{engine::EngineState, levenshtein_distance};
|
||||||
|
use nu_utils::IgnoreCaseExt;
|
||||||
use reedline::{Completer, Suggestion};
|
use reedline::{Completer, Suggestion};
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@ -13,21 +14,19 @@ impl NuHelpCompleter {
|
|||||||
|
|
||||||
fn completion_helper(&self, line: &str, pos: usize) -> Vec<Suggestion> {
|
fn completion_helper(&self, line: &str, pos: usize) -> Vec<Suggestion> {
|
||||||
let full_commands = self.0.get_signatures_with_examples(false);
|
let full_commands = self.0.get_signatures_with_examples(false);
|
||||||
|
let folded_line = line.to_folded_case();
|
||||||
|
|
||||||
//Vec<(Signature, Vec<Example>, bool, bool)> {
|
//Vec<(Signature, Vec<Example>, bool, bool)> {
|
||||||
let mut commands = full_commands
|
let mut commands = full_commands
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|(sig, _, _, _, _)| {
|
.filter(|(sig, _, _, _, _)| {
|
||||||
sig.name.to_lowercase().contains(&line.to_lowercase())
|
sig.name.to_folded_case().contains(&folded_line)
|
||||||
|| sig.usage.to_lowercase().contains(&line.to_lowercase())
|
|| sig.usage.to_folded_case().contains(&folded_line)
|
||||||
|| sig
|
|| sig
|
||||||
.search_terms
|
.search_terms
|
||||||
.iter()
|
.iter()
|
||||||
.any(|term| term.to_lowercase().contains(&line.to_lowercase()))
|
.any(|term| term.to_folded_case().contains(&folded_line))
|
||||||
|| sig
|
|| sig.extra_usage.to_folded_case().contains(&folded_line)
|
||||||
.extra_usage
|
|
||||||
.to_lowercase()
|
|
||||||
.contains(&line.to_lowercase())
|
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
@ -616,7 +616,7 @@ fn add_parsed_keybinding(
|
|||||||
let modifier = match keybinding
|
let modifier = match keybinding
|
||||||
.modifier
|
.modifier
|
||||||
.into_string("", config)
|
.into_string("", config)
|
||||||
.to_lowercase()
|
.to_ascii_lowercase()
|
||||||
.as_str()
|
.as_str()
|
||||||
{
|
{
|
||||||
"control" => KeyModifiers::CONTROL,
|
"control" => KeyModifiers::CONTROL,
|
||||||
@ -641,7 +641,7 @@ fn add_parsed_keybinding(
|
|||||||
let keycode = match keybinding
|
let keycode = match keybinding
|
||||||
.keycode
|
.keycode
|
||||||
.into_string("", config)
|
.into_string("", config)
|
||||||
.to_lowercase()
|
.to_ascii_lowercase()
|
||||||
.as_str()
|
.as_str()
|
||||||
{
|
{
|
||||||
"backspace" => KeyCode::Backspace,
|
"backspace" => KeyCode::Backspace,
|
||||||
@ -728,7 +728,7 @@ fn parse_event(value: &Value, config: &Config) -> Result<Option<ReedlineEvent>,
|
|||||||
match value {
|
match value {
|
||||||
Value::Record { val: record, .. } => match EventType::try_from_record(record, span)? {
|
Value::Record { val: record, .. } => match EventType::try_from_record(record, span)? {
|
||||||
EventType::Send(value) => event_from_record(
|
EventType::Send(value) => event_from_record(
|
||||||
value.into_string("", config).to_lowercase().as_str(),
|
value.into_string("", config).to_ascii_lowercase().as_str(),
|
||||||
record,
|
record,
|
||||||
config,
|
config,
|
||||||
span,
|
span,
|
||||||
@ -736,7 +736,7 @@ fn parse_event(value: &Value, config: &Config) -> Result<Option<ReedlineEvent>,
|
|||||||
.map(Some),
|
.map(Some),
|
||||||
EventType::Edit(value) => {
|
EventType::Edit(value) => {
|
||||||
let edit = edit_from_record(
|
let edit = edit_from_record(
|
||||||
value.into_string("", config).to_lowercase().as_str(),
|
value.into_string("", config).to_ascii_lowercase().as_str(),
|
||||||
record,
|
record,
|
||||||
config,
|
config,
|
||||||
span,
|
span,
|
||||||
|
@ -152,7 +152,7 @@ fn apply_window_spec(expr: Expr, window_type: Option<&WindowType>) -> Result<Exp
|
|||||||
fn parse_sql_function(sql_function: &SQLFunction) -> Result<Expr> {
|
fn parse_sql_function(sql_function: &SQLFunction) -> Result<Expr> {
|
||||||
use sqlparser::ast::{FunctionArg, FunctionArgExpr};
|
use sqlparser::ast::{FunctionArg, FunctionArgExpr};
|
||||||
// Function name mostly do not have name space, so it mostly take the first args
|
// Function name mostly do not have name space, so it mostly take the first args
|
||||||
let function_name = sql_function.name.0[0].value.to_lowercase();
|
let function_name = sql_function.name.0[0].value.to_ascii_lowercase();
|
||||||
let args = sql_function
|
let args = sql_function
|
||||||
.args
|
.args
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -7,6 +7,7 @@ use nu_protocol::{
|
|||||||
record, Category, Config, DataSource, Example, IntoPipelineData, PipelineData,
|
record, Category, Config, DataSource, Example, IntoPipelineData, PipelineData,
|
||||||
PipelineMetadata, ShellError, Signature, Spanned, SyntaxShape, Type, Value,
|
PipelineMetadata, ShellError, Signature, Spanned, SyntaxShape, Type, Value,
|
||||||
};
|
};
|
||||||
|
use nu_utils::IgnoreCaseExt;
|
||||||
use rust_embed::RustEmbed;
|
use rust_embed::RustEmbed;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
@ -180,7 +181,7 @@ fn get_theme_from_asset_file(
|
|||||||
let th = asset
|
let th = asset
|
||||||
.themes
|
.themes
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.find(|n| n.name.to_lowercase() == theme_name.to_lowercase()) // case insensitive search
|
.find(|n| n.name.eq_ignore_case(theme_name)) // case insensitive search
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
Ok(convert_html_theme_to_hash_map(is_dark, &th))
|
Ok(convert_html_theme_to_hash_map(is_dark, &th))
|
||||||
|
@ -578,7 +578,7 @@ fn fill_modifiers(attrs: &str, style: &mut Style) {
|
|||||||
//
|
//
|
||||||
// since we can combine styles like bold-italic, iterate through the chars
|
// since we can combine styles like bold-italic, iterate through the chars
|
||||||
// and set the bools for later use in the nu_ansi_term::Style application
|
// and set the bools for later use in the nu_ansi_term::Style application
|
||||||
for ch in attrs.to_lowercase().chars() {
|
for ch in attrs.chars().map(|c| c.to_ascii_lowercase()) {
|
||||||
match ch {
|
match ch {
|
||||||
'l' => style.is_blink = true,
|
'l' => style.is_blink = true,
|
||||||
'b' => style.is_bold = true,
|
'b' => style.is_bold = true,
|
||||||
|
@ -143,7 +143,7 @@ fn fill(
|
|||||||
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
|
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
|
||||||
|
|
||||||
let alignment = if let Some(arg) = alignment_arg {
|
let alignment = if let Some(arg) = alignment_arg {
|
||||||
match arg.to_lowercase().as_str() {
|
match arg.to_ascii_lowercase().as_str() {
|
||||||
"l" | "left" => FillAlignment::Left,
|
"l" | "left" => FillAlignment::Left,
|
||||||
"r" | "right" => FillAlignment::Right,
|
"r" | "right" => FillAlignment::Right,
|
||||||
"c" | "center" | "m" | "middle" => FillAlignment::Middle,
|
"c" | "center" | "m" | "middle" => FillAlignment::Middle,
|
||||||
|
@ -116,13 +116,13 @@ fn into_bool(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn string_to_boolean(s: &str, span: Span) -> Result<bool, ShellError> {
|
fn string_to_boolean(s: &str, span: Span) -> Result<bool, ShellError> {
|
||||||
match s.trim().to_lowercase().as_str() {
|
match s.trim().to_ascii_lowercase().as_str() {
|
||||||
"true" => Ok(true),
|
"true" => Ok(true),
|
||||||
"false" => Ok(false),
|
"false" => Ok(false),
|
||||||
o => {
|
o => {
|
||||||
let val = o.parse::<f64>();
|
let val = o.parse::<f64>();
|
||||||
match val {
|
match val {
|
||||||
Ok(f) => Ok(f.abs() >= f64::EPSILON),
|
Ok(f) => Ok(f != 0.0),
|
||||||
Err(_) => Err(ShellError::CantConvert {
|
Err(_) => Err(ShellError::CantConvert {
|
||||||
to_type: "boolean".to_string(),
|
to_type: "boolean".to_string(),
|
||||||
from_type: "string".to_string(),
|
from_type: "string".to_string(),
|
||||||
|
@ -46,7 +46,7 @@ impl Zone {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn from_string(s: String) -> Self {
|
fn from_string(s: String) -> Self {
|
||||||
match s.to_lowercase().as_str() {
|
match s.to_ascii_lowercase().as_str() {
|
||||||
"utc" | "u" => Self::Utc,
|
"utc" | "u" => Self::Utc,
|
||||||
"local" | "l" => Self::Local,
|
"local" | "l" => Self::Local,
|
||||||
_ => Self::Error,
|
_ => Self::Error,
|
||||||
|
@ -26,7 +26,7 @@ pub fn datetime_in_timezone(
|
|||||||
None => Err(ParseErrorKind::OutOfRange),
|
None => Err(ParseErrorKind::OutOfRange),
|
||||||
},
|
},
|
||||||
Err(ParseErrorKind::Invalid) => {
|
Err(ParseErrorKind::Invalid) => {
|
||||||
if s.to_lowercase() == "local" {
|
if s.eq_ignore_ascii_case("local") {
|
||||||
Ok(dt.with_timezone(Local::now().offset()))
|
Ok(dt.with_timezone(Local::now().offset()))
|
||||||
} else {
|
} else {
|
||||||
let tz: Tz = parse_timezone_internal(s)?;
|
let tz: Tz = parse_timezone_internal(s)?;
|
||||||
|
@ -10,6 +10,7 @@ use nu_protocol::{
|
|||||||
record, Category, Config, Example, IntoInterruptiblePipelineData, IntoPipelineData, ListStream,
|
record, Category, Config, Example, IntoInterruptiblePipelineData, IntoPipelineData, ListStream,
|
||||||
PipelineData, Record, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
PipelineData, Record, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
||||||
};
|
};
|
||||||
|
use nu_utils::IgnoreCaseExt;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Find;
|
pub struct Find;
|
||||||
@ -318,7 +319,9 @@ fn highlight_terms_in_record_with_search_columns(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn contains_ignore_case(string: &str, substring: &str) -> bool {
|
fn contains_ignore_case(string: &str, substring: &str) -> bool {
|
||||||
string.to_lowercase().contains(&substring.to_lowercase())
|
string
|
||||||
|
.to_folded_case()
|
||||||
|
.contains(&substring.to_folded_case())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_with_rest_and_highlight(
|
fn find_with_rest_and_highlight(
|
||||||
|
@ -5,6 +5,7 @@ use nu_protocol::{
|
|||||||
record, Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData,
|
record, Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData,
|
||||||
Record, ShellError, Signature, Span, Type, Value,
|
Record, ShellError, Signature, Span, Type, Value,
|
||||||
};
|
};
|
||||||
|
use nu_utils::IgnoreCaseExt;
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -220,14 +221,14 @@ fn sort_record(
|
|||||||
b.0.clone()
|
b.0.clone()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Convert to lowercase if case-insensitive
|
// Fold case if case-insensitive
|
||||||
let left = if insensitive {
|
let left = if insensitive {
|
||||||
left_res.to_ascii_lowercase()
|
left_res.to_folded_case()
|
||||||
} else {
|
} else {
|
||||||
left_res
|
left_res
|
||||||
};
|
};
|
||||||
let right = if insensitive {
|
let right = if insensitive {
|
||||||
right_res.to_ascii_lowercase()
|
right_res.to_folded_case()
|
||||||
} else {
|
} else {
|
||||||
right_res
|
right_res
|
||||||
};
|
};
|
||||||
@ -235,7 +236,7 @@ fn sort_record(
|
|||||||
if natural {
|
if natural {
|
||||||
compare_str(left, right)
|
compare_str(left, right)
|
||||||
} else {
|
} else {
|
||||||
left.partial_cmp(&right).unwrap_or(Ordering::Equal)
|
left.cmp(&right)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -262,28 +263,24 @@ pub fn sort(
|
|||||||
let span_a = a.span();
|
let span_a = a.span();
|
||||||
let span_b = b.span();
|
let span_b = b.span();
|
||||||
if insensitive {
|
if insensitive {
|
||||||
let lowercase_left = match a {
|
let folded_left = match a {
|
||||||
Value::String { val, .. } => {
|
Value::String { val, .. } => Value::string(val.to_folded_case(), span_a),
|
||||||
Value::string(val.to_ascii_lowercase(), span_a)
|
|
||||||
}
|
|
||||||
_ => a.clone(),
|
_ => a.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let lowercase_right = match b {
|
let folded_right = match b {
|
||||||
Value::String { val, .. } => {
|
Value::String { val, .. } => Value::string(val.to_folded_case(), span_b),
|
||||||
Value::string(val.to_ascii_lowercase(), span_b)
|
|
||||||
}
|
|
||||||
_ => b.clone(),
|
_ => b.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
if natural {
|
if natural {
|
||||||
match (lowercase_left.as_string(), lowercase_right.as_string()) {
|
match (folded_left.as_string(), folded_right.as_string()) {
|
||||||
(Ok(left), Ok(right)) => compare_str(left, right),
|
(Ok(left), Ok(right)) => compare_str(left, right),
|
||||||
_ => Ordering::Equal,
|
_ => Ordering::Equal,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
lowercase_left
|
folded_left
|
||||||
.partial_cmp(&lowercase_right)
|
.partial_cmp(&folded_right)
|
||||||
.unwrap_or(Ordering::Equal)
|
.unwrap_or(Ordering::Equal)
|
||||||
}
|
}
|
||||||
} else if natural {
|
} else if natural {
|
||||||
@ -326,23 +323,23 @@ pub fn process(
|
|||||||
let result = if insensitive {
|
let result = if insensitive {
|
||||||
let span_left = left_res.span();
|
let span_left = left_res.span();
|
||||||
let span_right = right_res.span();
|
let span_right = right_res.span();
|
||||||
let lowercase_left = match left_res {
|
let folded_left = match left_res {
|
||||||
Value::String { val, .. } => Value::string(val.to_ascii_lowercase(), span_left),
|
Value::String { val, .. } => Value::string(val.to_folded_case(), span_left),
|
||||||
_ => left_res,
|
_ => left_res,
|
||||||
};
|
};
|
||||||
|
|
||||||
let lowercase_right = match right_res {
|
let folded_right = match right_res {
|
||||||
Value::String { val, .. } => Value::string(val.to_ascii_lowercase(), span_right),
|
Value::String { val, .. } => Value::string(val.to_folded_case(), span_right),
|
||||||
_ => right_res,
|
_ => right_res,
|
||||||
};
|
};
|
||||||
if natural {
|
if natural {
|
||||||
match (lowercase_left.as_string(), lowercase_right.as_string()) {
|
match (folded_left.as_string(), folded_right.as_string()) {
|
||||||
(Ok(left), Ok(right)) => compare_str(left, right),
|
(Ok(left), Ok(right)) => compare_str(left, right),
|
||||||
_ => Ordering::Equal,
|
_ => Ordering::Equal,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
lowercase_left
|
folded_left
|
||||||
.partial_cmp(&lowercase_right)
|
.partial_cmp(&folded_right)
|
||||||
.unwrap_or(Ordering::Equal)
|
.unwrap_or(Ordering::Equal)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -6,6 +6,7 @@ use nu_protocol::{
|
|||||||
record, Category, Example, IntoPipelineData, PipelineData, PipelineMetadata, ShellError,
|
record, Category, Example, IntoPipelineData, PipelineData, PipelineMetadata, ShellError,
|
||||||
Signature, Span, Type, Value,
|
Signature, Span, Type, Value,
|
||||||
};
|
};
|
||||||
|
use nu_utils::IgnoreCaseExt;
|
||||||
use std::collections::hash_map::IntoIter;
|
use std::collections::hash_map::IntoIter;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
@ -172,7 +173,7 @@ impl ValueCounter {
|
|||||||
ValueCounter {
|
ValueCounter {
|
||||||
val,
|
val,
|
||||||
val_to_compare: if flag_ignore_case {
|
val_to_compare: if flag_ignore_case {
|
||||||
clone_to_lowercase(&vals_to_compare.with_span(Span::unknown()))
|
clone_to_folded_case(&vals_to_compare.with_span(Span::unknown()))
|
||||||
} else {
|
} else {
|
||||||
vals_to_compare.with_span(Span::unknown())
|
vals_to_compare.with_span(Span::unknown())
|
||||||
},
|
},
|
||||||
@ -182,17 +183,17 @@ impl ValueCounter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clone_to_lowercase(value: &Value) -> Value {
|
fn clone_to_folded_case(value: &Value) -> Value {
|
||||||
let span = value.span();
|
let span = value.span();
|
||||||
match value {
|
match value {
|
||||||
Value::String { val: s, .. } => Value::string(s.clone().to_lowercase(), span),
|
Value::String { val: s, .. } => Value::string(s.clone().to_folded_case(), span),
|
||||||
Value::List { vals: vec, .. } => {
|
Value::List { vals: vec, .. } => {
|
||||||
Value::list(vec.iter().map(clone_to_lowercase).collect(), span)
|
Value::list(vec.iter().map(clone_to_folded_case).collect(), span)
|
||||||
}
|
}
|
||||||
Value::Record { val: record, .. } => Value::record(
|
Value::Record { val: record, .. } => Value::record(
|
||||||
record
|
record
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(k, v)| (k.to_owned(), clone_to_lowercase(v)))
|
.map(|(k, v)| (k.to_owned(), clone_to_folded_case(v)))
|
||||||
.collect(),
|
.collect(),
|
||||||
span,
|
span,
|
||||||
),
|
),
|
||||||
|
@ -150,9 +150,9 @@ used as the next argument to the closure, otherwise generation stops.
|
|||||||
let mut err = None;
|
let mut err = None;
|
||||||
|
|
||||||
for (k, v) in iter {
|
for (k, v) in iter {
|
||||||
if k.to_lowercase() == "out" {
|
if k.eq_ignore_ascii_case("out") {
|
||||||
out = Some(v);
|
out = Some(v);
|
||||||
} else if k.to_lowercase() == "next" {
|
} else if k.eq_ignore_ascii_case("next") {
|
||||||
next = Some(v);
|
next = Some(v);
|
||||||
} else {
|
} else {
|
||||||
let error = ShellError::GenericError(
|
let error = ShellError::GenericError(
|
||||||
|
@ -162,9 +162,9 @@ used as the next argument to the closure, otherwise generation stops.
|
|||||||
let mut err = None;
|
let mut err = None;
|
||||||
|
|
||||||
for (k, v) in iter {
|
for (k, v) in iter {
|
||||||
if k.to_lowercase() == "out" {
|
if k.eq_ignore_ascii_case("out") {
|
||||||
out = Some(v);
|
out = Some(v);
|
||||||
} else if k.to_lowercase() == "next" {
|
} else if k.eq_ignore_ascii_case("next") {
|
||||||
next = Some(v);
|
next = Some(v);
|
||||||
} else {
|
} else {
|
||||||
let error = ShellError::GenericError(
|
let error = ShellError::GenericError(
|
||||||
|
@ -10,6 +10,7 @@ use nu_protocol::{
|
|||||||
span, Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Spanned,
|
span, Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Spanned,
|
||||||
SyntaxShape, Type, Value,
|
SyntaxShape, Type, Value,
|
||||||
};
|
};
|
||||||
|
use nu_utils::IgnoreCaseExt;
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Help;
|
pub struct Help;
|
||||||
|
|
||||||
@ -144,7 +145,7 @@ pub fn highlight_search_in_table(
|
|||||||
highlight_style: &Style,
|
highlight_style: &Style,
|
||||||
) -> Result<Vec<Value>, ShellError> {
|
) -> Result<Vec<Value>, ShellError> {
|
||||||
let orig_search_string = search_string;
|
let orig_search_string = search_string;
|
||||||
let search_string = search_string.to_lowercase();
|
let search_string = search_string.to_folded_case();
|
||||||
let mut matches = vec![];
|
let mut matches = vec![];
|
||||||
|
|
||||||
for record in table {
|
for record in table {
|
||||||
@ -168,7 +169,7 @@ pub fn highlight_search_in_table(
|
|||||||
}
|
}
|
||||||
let span = val.span();
|
let span = val.span();
|
||||||
if let Value::String { val: s, .. } = val {
|
if let Value::String { val: s, .. } = val {
|
||||||
if s.to_lowercase().contains(&search_string) {
|
if s.to_folded_case().contains(&search_string) {
|
||||||
*val = Value::string(
|
*val = Value::string(
|
||||||
highlight_search_string(
|
highlight_search_string(
|
||||||
s,
|
s,
|
||||||
|
@ -134,7 +134,7 @@ fn build_help_commands(engine_state: &EngineState, span: Span) -> Vec<Value> {
|
|||||||
let usage = sig.usage;
|
let usage = sig.usage;
|
||||||
let search_terms = sig.search_terms;
|
let search_terms = sig.search_terms;
|
||||||
|
|
||||||
let command_type = format!("{:?}", decl.command_type()).to_lowercase();
|
let command_type = format!("{:?}", decl.command_type()).to_ascii_lowercase();
|
||||||
|
|
||||||
// Build table of parameters
|
// Build table of parameters
|
||||||
let param_table = {
|
let param_table = {
|
||||||
|
@ -345,9 +345,9 @@ fn get_keycode_name(head: Span, code: &KeyCode) -> (Value, Value) {
|
|||||||
let (typ, code) = match code {
|
let (typ, code) = match code {
|
||||||
KeyCode::F(n) => ("f", n.to_string()),
|
KeyCode::F(n) => ("f", n.to_string()),
|
||||||
KeyCode::Char(c) => ("char", c.to_string()),
|
KeyCode::Char(c) => ("char", c.to_string()),
|
||||||
KeyCode::Media(m) => ("media", format!("{m:?}").to_lowercase()),
|
KeyCode::Media(m) => ("media", format!("{m:?}").to_ascii_lowercase()),
|
||||||
KeyCode::Modifier(m) => ("modifier", format!("{m:?}").to_lowercase()),
|
KeyCode::Modifier(m) => ("modifier", format!("{m:?}").to_ascii_lowercase()),
|
||||||
_ => ("other", format!("{code:?}").to_lowercase()),
|
_ => ("other", format!("{code:?}").to_ascii_lowercase()),
|
||||||
};
|
};
|
||||||
(Value::string(typ, head), Value::string(code, head))
|
(Value::string(typ, head), Value::string(code, head))
|
||||||
}
|
}
|
||||||
@ -365,7 +365,7 @@ fn parse_modifiers(head: Span, modifiers: &KeyModifiers) -> Value {
|
|||||||
let parsed_modifiers = ALL_MODIFIERS
|
let parsed_modifiers = ALL_MODIFIERS
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|m| modifiers.contains(**m))
|
.filter(|m| modifiers.contains(**m))
|
||||||
.map(|m| format!("{m:?}").to_lowercase())
|
.map(|m| format!("{m:?}").to_ascii_lowercase())
|
||||||
.map(|string| Value::string(string, head))
|
.map(|string| Value::string(string, head))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use alphanumeric_sort::compare_str;
|
use alphanumeric_sort::compare_str;
|
||||||
use nu_engine::column::nonexistent_column;
|
use nu_engine::column::nonexistent_column;
|
||||||
use nu_protocol::{ShellError, Span, Value};
|
use nu_protocol::{ShellError, Span, Value};
|
||||||
|
use nu_utils::IgnoreCaseExt;
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
// This module includes sorting functionality that is useful in sort-by and elsewhere.
|
// This module includes sorting functionality that is useful in sort-by and elsewhere.
|
||||||
@ -125,28 +126,24 @@ pub fn sort(
|
|||||||
if insensitive {
|
if insensitive {
|
||||||
let span_a = a.span();
|
let span_a = a.span();
|
||||||
let span_b = b.span();
|
let span_b = b.span();
|
||||||
let lowercase_left = match a {
|
let folded_left = match a {
|
||||||
Value::String { val, .. } => {
|
Value::String { val, .. } => Value::string(val.to_folded_case(), span_a),
|
||||||
Value::string(val.to_ascii_lowercase(), span_a)
|
|
||||||
}
|
|
||||||
_ => a.clone(),
|
_ => a.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let lowercase_right = match b {
|
let folded_right = match b {
|
||||||
Value::String { val, .. } => {
|
Value::String { val, .. } => Value::string(val.to_folded_case(), span_b),
|
||||||
Value::string(val.to_ascii_lowercase(), span_b)
|
|
||||||
}
|
|
||||||
_ => b.clone(),
|
_ => b.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
if natural {
|
if natural {
|
||||||
match (lowercase_left.as_string(), lowercase_right.as_string()) {
|
match (folded_left.as_string(), folded_right.as_string()) {
|
||||||
(Ok(left), Ok(right)) => compare_str(left, right),
|
(Ok(left), Ok(right)) => compare_str(left, right),
|
||||||
_ => Ordering::Equal,
|
_ => Ordering::Equal,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
lowercase_left
|
folded_left
|
||||||
.partial_cmp(&lowercase_right)
|
.partial_cmp(&folded_right)
|
||||||
.unwrap_or(Ordering::Equal)
|
.unwrap_or(Ordering::Equal)
|
||||||
}
|
}
|
||||||
} else if natural {
|
} else if natural {
|
||||||
@ -189,23 +186,23 @@ pub fn compare(
|
|||||||
let result = if insensitive {
|
let result = if insensitive {
|
||||||
let span_left = left_res.span();
|
let span_left = left_res.span();
|
||||||
let span_right = right_res.span();
|
let span_right = right_res.span();
|
||||||
let lowercase_left = match left_res {
|
let folded_left = match left_res {
|
||||||
Value::String { val, .. } => Value::string(val.to_ascii_lowercase(), span_left),
|
Value::String { val, .. } => Value::string(val.to_folded_case(), span_left),
|
||||||
_ => left_res,
|
_ => left_res,
|
||||||
};
|
};
|
||||||
|
|
||||||
let lowercase_right = match right_res {
|
let folded_right = match right_res {
|
||||||
Value::String { val, .. } => Value::string(val.to_ascii_lowercase(), span_right),
|
Value::String { val, .. } => Value::string(val.to_folded_case(), span_right),
|
||||||
_ => right_res,
|
_ => right_res,
|
||||||
};
|
};
|
||||||
if natural {
|
if natural {
|
||||||
match (lowercase_left.as_string(), lowercase_right.as_string()) {
|
match (folded_left.as_string(), folded_right.as_string()) {
|
||||||
(Ok(left), Ok(right)) => compare_str(left, right),
|
(Ok(left), Ok(right)) => compare_str(left, right),
|
||||||
_ => Ordering::Equal,
|
_ => Ordering::Equal,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
lowercase_left
|
folded_left
|
||||||
.partial_cmp(&lowercase_right)
|
.partial_cmp(&folded_right)
|
||||||
.unwrap_or(Ordering::Equal)
|
.unwrap_or(Ordering::Equal)
|
||||||
}
|
}
|
||||||
} else if natural {
|
} else if natural {
|
||||||
|
@ -28,7 +28,7 @@ pub fn decode(
|
|||||||
bytes: &[u8],
|
bytes: &[u8],
|
||||||
) -> Result<Value, ShellError> {
|
) -> Result<Value, ShellError> {
|
||||||
// Workaround for a bug in the Encodings Specification.
|
// Workaround for a bug in the Encodings Specification.
|
||||||
let encoding = if encoding_name.item.to_lowercase() == "utf16" {
|
let encoding = if encoding_name.item.eq_ignore_ascii_case("utf16") {
|
||||||
parse_encoding(encoding_name.span, "utf-16")
|
parse_encoding(encoding_name.span, "utf-16")
|
||||||
} else {
|
} else {
|
||||||
parse_encoding(encoding_name.span, &encoding_name.item)
|
parse_encoding(encoding_name.span, &encoding_name.item)
|
||||||
@ -45,7 +45,7 @@ pub fn encode(
|
|||||||
ignore_errors: bool,
|
ignore_errors: bool,
|
||||||
) -> Result<Value, ShellError> {
|
) -> Result<Value, ShellError> {
|
||||||
// Workaround for a bug in the Encodings Specification.
|
// Workaround for a bug in the Encodings Specification.
|
||||||
let encoding = if encoding_name.item.to_lowercase() == "utf16" {
|
let encoding = if encoding_name.item.eq_ignore_ascii_case("utf16") {
|
||||||
parse_encoding(encoding_name.span, "utf-16")
|
parse_encoding(encoding_name.span, "utf-16")
|
||||||
} else {
|
} else {
|
||||||
parse_encoding(encoding_name.span, &encoding_name.item)
|
parse_encoding(encoding_name.span, &encoding_name.item)
|
||||||
@ -69,7 +69,7 @@ pub fn encode(
|
|||||||
|
|
||||||
fn parse_encoding(span: Span, label: &str) -> Result<&'static Encoding, ShellError> {
|
fn parse_encoding(span: Span, label: &str) -> Result<&'static Encoding, ShellError> {
|
||||||
// Workaround for a bug in the Encodings Specification.
|
// Workaround for a bug in the Encodings Specification.
|
||||||
let label = if label.to_lowercase() == "utf16" {
|
let label = if label.eq_ignore_ascii_case("utf16") {
|
||||||
"utf-16"
|
"utf-16"
|
||||||
} else {
|
} else {
|
||||||
label
|
label
|
||||||
|
@ -7,6 +7,7 @@ use nu_protocol::record;
|
|||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
||||||
};
|
};
|
||||||
|
use nu_utils::IgnoreCaseExt;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SubCommand;
|
pub struct SubCommand;
|
||||||
@ -153,11 +154,11 @@ fn action(
|
|||||||
match case_insensitive {
|
match case_insensitive {
|
||||||
true => {
|
true => {
|
||||||
if *not_contain {
|
if *not_contain {
|
||||||
!val.to_lowercase()
|
!val.to_folded_case()
|
||||||
.contains(substring.to_lowercase().as_str())
|
.contains(substring.to_folded_case().as_str())
|
||||||
} else {
|
} else {
|
||||||
val.to_lowercase()
|
val.to_folded_case()
|
||||||
.contains(substring.to_lowercase().as_str())
|
.contains(substring.to_folded_case().as_str())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
false => {
|
false => {
|
||||||
|
@ -5,6 +5,7 @@ use nu_protocol::ast::CellPath;
|
|||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
use nu_protocol::Category;
|
use nu_protocol::Category;
|
||||||
use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value};
|
use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value};
|
||||||
|
use nu_utils::IgnoreCaseExt;
|
||||||
|
|
||||||
struct Arguments {
|
struct Arguments {
|
||||||
substring: String,
|
substring: String,
|
||||||
@ -98,7 +99,8 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
|
|||||||
match input {
|
match input {
|
||||||
Value::String { val: s, .. } => {
|
Value::String { val: s, .. } => {
|
||||||
let ends_with = if args.case_insensitive {
|
let ends_with = if args.case_insensitive {
|
||||||
s.to_lowercase().ends_with(&args.substring.to_lowercase())
|
s.to_folded_case()
|
||||||
|
.ends_with(&args.substring.to_folded_case())
|
||||||
} else {
|
} else {
|
||||||
s.ends_with(&args.substring)
|
s.ends_with(&args.substring)
|
||||||
};
|
};
|
||||||
|
@ -6,6 +6,7 @@ use nu_protocol::engine::{Command, EngineState, Stack};
|
|||||||
use nu_protocol::Category;
|
use nu_protocol::Category;
|
||||||
use nu_protocol::Spanned;
|
use nu_protocol::Spanned;
|
||||||
use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value};
|
use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value};
|
||||||
|
use nu_utils::IgnoreCaseExt;
|
||||||
|
|
||||||
struct Arguments {
|
struct Arguments {
|
||||||
substring: String,
|
substring: String,
|
||||||
@ -111,7 +112,7 @@ fn action(
|
|||||||
match input {
|
match input {
|
||||||
Value::String { val: s, .. } => {
|
Value::String { val: s, .. } => {
|
||||||
let starts_with = if *case_insensitive {
|
let starts_with = if *case_insensitive {
|
||||||
s.to_lowercase().starts_with(&substring.to_lowercase())
|
s.to_folded_case().starts_with(&substring.to_folded_case())
|
||||||
} else {
|
} else {
|
||||||
s.starts_with(substring)
|
s.starts_with(substring)
|
||||||
};
|
};
|
||||||
|
@ -9,6 +9,7 @@ use nu_protocol::{
|
|||||||
SyntaxShape, Type, Value,
|
SyntaxShape, Type, Value,
|
||||||
};
|
};
|
||||||
use nu_system::ForegroundProcess;
|
use nu_system::ForegroundProcess;
|
||||||
|
use nu_utils::IgnoreCaseExt;
|
||||||
use os_pipe::PipeReader;
|
use os_pipe::PipeReader;
|
||||||
use pathdiff::diff_paths;
|
use pathdiff::diff_paths;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
@ -223,10 +224,10 @@ impl ExternalCommand {
|
|||||||
const CMD_INTERNAL_COMMANDS: [&str; 9] = [
|
const CMD_INTERNAL_COMMANDS: [&str; 9] = [
|
||||||
"ASSOC", "CLS", "ECHO", "FTYPE", "MKLINK", "PAUSE", "START", "VER", "VOL",
|
"ASSOC", "CLS", "ECHO", "FTYPE", "MKLINK", "PAUSE", "START", "VER", "VOL",
|
||||||
];
|
];
|
||||||
let command_name_upper = self.name.item.to_uppercase();
|
let command_name = &self.name.item;
|
||||||
let looks_like_cmd_internal = CMD_INTERNAL_COMMANDS
|
let looks_like_cmd_internal = CMD_INTERNAL_COMMANDS
|
||||||
.iter()
|
.iter()
|
||||||
.any(|&cmd| command_name_upper == cmd);
|
.any(|&cmd| command_name.eq_ignore_ascii_case(cmd));
|
||||||
|
|
||||||
if looks_like_cmd_internal {
|
if looks_like_cmd_internal {
|
||||||
let (cmd, new_reader) = self.create_process(&input, true, head)?;
|
let (cmd, new_reader) = self.create_process(&input, true, head)?;
|
||||||
@ -252,9 +253,10 @@ impl ExternalCommand {
|
|||||||
which::which_in(&self.name.item, Some(path_with_cwd), cwd)
|
which::which_in(&self.name.item, Some(path_with_cwd), cwd)
|
||||||
{
|
{
|
||||||
if let Some(file_name) = which_path.file_name() {
|
if let Some(file_name) = which_path.file_name() {
|
||||||
let file_name_upper =
|
if !file_name
|
||||||
file_name.to_string_lossy().to_uppercase();
|
.to_string_lossy()
|
||||||
if file_name_upper != command_name_upper {
|
.eq_ignore_case(command_name)
|
||||||
|
{
|
||||||
// which-rs found an executable file with a slightly different name
|
// which-rs found an executable file with a slightly different name
|
||||||
// than the one the user tried. Let's try running it
|
// than the one the user tried. Let's try running it
|
||||||
let mut new_command = self.clone();
|
let mut new_command = self.clone();
|
||||||
@ -767,11 +769,11 @@ fn trim_expand_and_apply_arg(
|
|||||||
/// Given an invalid command name, try to suggest an alternative
|
/// Given an invalid command name, try to suggest an alternative
|
||||||
fn suggest_command(attempted_command: &str, engine_state: &EngineState) -> Option<String> {
|
fn suggest_command(attempted_command: &str, engine_state: &EngineState) -> Option<String> {
|
||||||
let commands = engine_state.get_signatures(false);
|
let commands = engine_state.get_signatures(false);
|
||||||
let command_name_lower = attempted_command.to_lowercase();
|
let command_folded_case = attempted_command.to_folded_case();
|
||||||
let search_term_match = commands.iter().find(|sig| {
|
let search_term_match = commands.iter().find(|sig| {
|
||||||
sig.search_terms
|
sig.search_terms
|
||||||
.iter()
|
.iter()
|
||||||
.any(|term| term.to_lowercase() == command_name_lower)
|
.any(|term| term.to_folded_case() == command_folded_case)
|
||||||
});
|
});
|
||||||
match search_term_match {
|
match search_term_match {
|
||||||
Some(sig) => Some(sig.name.clone()),
|
Some(sig) => Some(sig.name.clone()),
|
||||||
|
@ -1016,29 +1016,19 @@ fn in_char_specifiers(specifiers: &[CharSpecifier], c: char, options: MatchOptio
|
|||||||
CharRange(start, end) => {
|
CharRange(start, end) => {
|
||||||
// FIXME: work with non-ascii chars properly (issue #1347)
|
// FIXME: work with non-ascii chars properly (issue #1347)
|
||||||
if !options.case_sensitive && c.is_ascii() && start.is_ascii() && end.is_ascii() {
|
if !options.case_sensitive && c.is_ascii() && start.is_ascii() && end.is_ascii() {
|
||||||
let start = start.to_ascii_lowercase();
|
|
||||||
let end = end.to_ascii_lowercase();
|
|
||||||
|
|
||||||
let start_up = start
|
|
||||||
.to_uppercase()
|
|
||||||
.next()
|
|
||||||
.expect("internal error: getting start uppercase");
|
|
||||||
let end_up = end
|
|
||||||
.to_uppercase()
|
|
||||||
.next()
|
|
||||||
.expect("internal error: getting end uppercase");
|
|
||||||
|
|
||||||
// only allow case insensitive matching when
|
// only allow case insensitive matching when
|
||||||
// both start and end are within a-z or A-Z
|
// both start and end are within a-z or A-Z
|
||||||
if start != start_up && end != end_up {
|
if start.is_ascii_alphabetic() && end.is_ascii_alphabetic() {
|
||||||
|
let start = start.to_ascii_lowercase();
|
||||||
|
let end = end.to_ascii_lowercase();
|
||||||
let c = c.to_ascii_lowercase();
|
let c = c.to_ascii_lowercase();
|
||||||
if c >= start && c <= end {
|
if (start..=end).contains(&c) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if c >= start && c <= end {
|
if (start..=end).contains(&c) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1279,7 +1269,7 @@ mod test {
|
|||||||
fn test_range_pattern() {
|
fn test_range_pattern() {
|
||||||
let pat = Pattern::new("a[0-9]b").unwrap();
|
let pat = Pattern::new("a[0-9]b").unwrap();
|
||||||
for i in 0..10 {
|
for i in 0..10 {
|
||||||
assert!(pat.matches(&format!("a{}b", i)));
|
assert!(pat.matches(&format!("a{}b", i)), "a{i}b =~ a[0-9]b");
|
||||||
}
|
}
|
||||||
assert!(!pat.matches("a_b"));
|
assert!(!pat.matches("a_b"));
|
||||||
|
|
||||||
|
@ -2199,7 +2199,7 @@ pub fn parse_filesize(working_set: &mut StateWorkingSet, span: Span) -> Expressi
|
|||||||
}
|
}
|
||||||
|
|
||||||
match parse_unit_value(bytes, span, FILESIZE_UNIT_GROUPS, Type::Filesize, |x| {
|
match parse_unit_value(bytes, span, FILESIZE_UNIT_GROUPS, Type::Filesize, |x| {
|
||||||
x.to_uppercase()
|
x.to_ascii_uppercase()
|
||||||
}) {
|
}) {
|
||||||
Some(Ok(expr)) => expr,
|
Some(Ok(expr)) => expr,
|
||||||
Some(Err(mk_err_for)) => {
|
Some(Err(mk_err_for)) => {
|
||||||
|
@ -8,7 +8,7 @@ where
|
|||||||
crate::lev_distance::find_best_match_for_name_with_substrings(&possibilities, input, None)
|
crate::lev_distance::find_best_match_for_name_with_substrings(&possibilities, input, None)
|
||||||
.map(|s| s.to_string());
|
.map(|s| s.to_string());
|
||||||
if let Some(suggestion) = &suggestion {
|
if let Some(suggestion) = &suggestion {
|
||||||
if suggestion.len() == 1 && suggestion.to_lowercase() != input.to_lowercase() {
|
if suggestion.len() == 1 && !suggestion.eq_ignore_ascii_case(input) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,8 +20,8 @@ pub use custom_value::CustomValue;
|
|||||||
use fancy_regex::Regex;
|
use fancy_regex::Regex;
|
||||||
pub use from_value::FromValue;
|
pub use from_value::FromValue;
|
||||||
pub use lazy_record::LazyRecord;
|
pub use lazy_record::LazyRecord;
|
||||||
use nu_utils::get_system_locale;
|
|
||||||
use nu_utils::locale::get_system_locale_string;
|
use nu_utils::locale::get_system_locale_string;
|
||||||
|
use nu_utils::{get_system_locale, IgnoreCaseExt};
|
||||||
use num_format::ToFormattedString;
|
use num_format::ToFormattedString;
|
||||||
pub use range::*;
|
pub use range::*;
|
||||||
pub use record::Record;
|
pub use record::Record;
|
||||||
@ -1008,7 +1008,7 @@ impl Value {
|
|||||||
// Make reverse iterate to avoid duplicate column leads to first value, actually last value is expected.
|
// Make reverse iterate to avoid duplicate column leads to first value, actually last value is expected.
|
||||||
if let Some(found) = val.iter().rev().find(|x| {
|
if let Some(found) = val.iter().rev().find(|x| {
|
||||||
if insensitive {
|
if insensitive {
|
||||||
x.0.to_lowercase() == column_name.to_lowercase()
|
x.0.eq_ignore_case(column_name)
|
||||||
} else {
|
} else {
|
||||||
x.0 == column_name
|
x.0 == column_name
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ lscolors = { version = "0.15", default-features = false, features = ["nu-ansi-te
|
|||||||
num-format = { version = "0.4" }
|
num-format = { version = "0.4" }
|
||||||
strip-ansi-escapes = "0.2.0"
|
strip-ansi-escapes = "0.2.0"
|
||||||
sys-locale = "0.3"
|
sys-locale = "0.3"
|
||||||
|
unicase = "2.7.0"
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
crossterm_winapi = "0.9"
|
crossterm_winapi = "0.9"
|
||||||
|
55
crates/nu-utils/src/casing.rs
Normal file
55
crates/nu-utils/src/casing.rs
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
use std::cmp::Ordering;
|
||||||
|
use unicase::UniCase;
|
||||||
|
|
||||||
|
pub trait IgnoreCaseExt {
|
||||||
|
/// Returns a [case folded] equivalent of this string, as a new String.
|
||||||
|
///
|
||||||
|
/// Case folding is primarily based on lowercase mapping, but includes
|
||||||
|
/// additional changes to the source text to help make case folding
|
||||||
|
/// language-invariant and consistent. Case folded text should be used
|
||||||
|
/// solely for processing and generally should not be stored or displayed.
|
||||||
|
///
|
||||||
|
/// Note: this method might only do [`str::to_lowercase`] instead of a
|
||||||
|
/// full case fold, depending on how Nu is compiled. You should still
|
||||||
|
/// prefer using this method for generating case-insensitive strings,
|
||||||
|
/// though, as it expresses intent much better than `to_lowercase`.
|
||||||
|
///
|
||||||
|
/// [case folded]: <https://unicode.org/faq/casemap_charprop.html#2>
|
||||||
|
fn to_folded_case(&self) -> String;
|
||||||
|
|
||||||
|
/// Checks that two strings are a case-insensitive match.
|
||||||
|
///
|
||||||
|
/// Essentially `to_folded_case(a) == to_folded_case(b)`, but without
|
||||||
|
/// allocating and copying string temporaries. Because case folding involves
|
||||||
|
/// Unicode table lookups, it can sometimes be more efficient to use
|
||||||
|
/// `to_folded_case` to case fold once and then compare those strings.
|
||||||
|
fn eq_ignore_case(&self, other: &str) -> bool;
|
||||||
|
|
||||||
|
/// Compares two strings case-insensitively.
|
||||||
|
///
|
||||||
|
/// Essentially `to_folded_case(a) == to_folded_case(b)`, but without
|
||||||
|
/// allocating and copying string temporaries. Because case folding involves
|
||||||
|
/// Unicode table lookups, it can sometimes be more efficient to use
|
||||||
|
/// `to_folded_case` to case fold once and then compare those strings.
|
||||||
|
///
|
||||||
|
/// Note that this *only* ignores case, comparing the folded strings without
|
||||||
|
/// any other collation data or locale, so the sort order may be surprising
|
||||||
|
/// outside of ASCII characters.
|
||||||
|
fn cmp_ignore_case(&self, other: &str) -> Ordering;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IgnoreCaseExt for str {
|
||||||
|
fn to_folded_case(&self) -> String {
|
||||||
|
// we only do to_lowercase, as unicase doesn't expose its case fold yet
|
||||||
|
// (seanmonstar/unicase#61) and we don't want to pull in another table
|
||||||
|
self.to_lowercase()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eq_ignore_case(&self, other: &str) -> bool {
|
||||||
|
UniCase::new(self) == UniCase::new(other)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cmp_ignore_case(&self, other: &str) -> Ordering {
|
||||||
|
UniCase::new(self).cmp(&UniCase::new(other))
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,4 @@
|
|||||||
|
mod casing;
|
||||||
pub mod ctrl_c;
|
pub mod ctrl_c;
|
||||||
mod deansi;
|
mod deansi;
|
||||||
pub mod locale;
|
pub mod locale;
|
||||||
@ -9,6 +10,7 @@ pub use utils::{
|
|||||||
stderr_write_all_and_flush, stdout_write_all_and_flush,
|
stderr_write_all_and_flush, stdout_write_all_and_flush,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub use casing::IgnoreCaseExt;
|
||||||
pub use deansi::{
|
pub use deansi::{
|
||||||
strip_ansi_likely, strip_ansi_string_likely, strip_ansi_string_unlikely, strip_ansi_unlikely,
|
strip_ansi_likely, strip_ansi_string_likely, strip_ansi_string_unlikely, strip_ansi_unlikely,
|
||||||
};
|
};
|
||||||
|
@ -70,8 +70,8 @@ pub(crate) fn read_config_file(
|
|||||||
get_default_config()
|
get_default_config()
|
||||||
};
|
};
|
||||||
|
|
||||||
match answer.to_lowercase().trim() {
|
match answer.trim() {
|
||||||
"y" | "" => {
|
"y" | "Y" | "" => {
|
||||||
if let Ok(mut output) = File::create(&config_path) {
|
if let Ok(mut output) = File::create(&config_path) {
|
||||||
if write!(output, "{config_file}").is_ok() {
|
if write!(output, "{config_file}").is_ok() {
|
||||||
let config_type = if is_env_config {
|
let config_type = if is_env_config {
|
||||||
|
Loading…
Reference in New Issue
Block a user