io: accept intoCharIter in more functions

This makes them more general than the previous versions which expected
`&wstr`. It comes at the cost of additional eager calls to `chars()`.

To implement `appendln` without having to call `append` twice, implement
`IntoCharIter` for chained iterators whose elements are both the kind of
iterator specified by `IntoCharIter`.

Because `IntoCharIter` is not implemented for owned types to avoid
allocations, some call sites which used to pass `WString` need to be
updated to pass references instead, which constitutes the bulk of the
changes in this commit.

Part of #12396
This commit is contained in:
Daniel Rainer
2026-01-30 21:21:37 +01:00
committed by Johannes Altmanninger
parent 934def3b75
commit 414dba994f
17 changed files with 59 additions and 53 deletions

View File

@@ -240,6 +240,15 @@ fn extend_wstring(&self, out: &mut WString) -> bool {
}
}
impl<A: DoubleEndedIterator<Item = char> + Clone, B: DoubleEndedIterator<Item = char> + Clone>
IntoCharIter for std::iter::Chain<A, B>
{
type Iter = std::iter::Chain<A, B>;
fn chars(self) -> Self::Iter {
self
}
}
/// Return true if `prefix` is a prefix of `contents`.
fn iter_prefixes_iter<Prefix, Contents>(prefix: Prefix, mut contents: Contents) -> bool
where

View File

@@ -428,7 +428,7 @@ fn abbr_add(opts: &Options, streams: &mut IoStreams) -> BuiltinResult {
}
});
if !opts.commands.is_empty() && position == Position::Command {
streams.err.appendln(wgettext_fmt!(
streams.err.appendln(&wgettext_fmt!(
"%s: --command cannot be combined with --position=command",
CMD,
));

View File

@@ -401,7 +401,7 @@ fn list_modes(&mut self, streams: &mut IoStreams) {
modes.dedup();
for mode in modes {
streams.out.appendln(mode);
streams.out.appendln(&mode);
}
}
}

View File

@@ -232,7 +232,7 @@ fn write_part(
}
for arg in args {
streams.out.appendln(arg.completion);
streams.out.appendln(&arg.completion);
}
}

View File

@@ -65,7 +65,7 @@ pub fn contains(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) ->
for (i, arg) in args[optind..].iter().enumerate().skip(1) {
if needle == arg {
if opts.print_index {
streams.out.appendln(i.to_wstring());
streams.out.appendln(&i.to_wstring());
}
return Ok(SUCCESS);
}

View File

@@ -19,7 +19,7 @@ pub fn count(_parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> B
.filter(|input_value| input_value.want_newline)
.count();
streams.out.appendln(numargs.to_wstring());
streams.out.appendln(&numargs.to_wstring());
if numargs == 0 {
return Err(STATUS_CMD_ERROR);

View File

@@ -1007,7 +1007,7 @@ enum OutputType {
return Ok(SUCCESS);
}
'v' => {
streams.out.appendln(wgettext_fmt!(
streams.out.appendln(&wgettext_fmt!(
"%s, version %s",
get_program_name(),
crate::BUILD_VERSION
@@ -1051,7 +1051,7 @@ enum OutputType {
while i < args.len() || (args.is_empty() && i == 0) {
if args.is_empty() && i == 0 {
if output_type == OutputType::File {
streams.err.appendln(wgettext_fmt!(
streams.err.appendln(&wgettext_fmt!(
"Expected file path to read/write for -w:\n\n $ %s -w foo.fish",
get_program_name()
));
@@ -1087,7 +1087,7 @@ enum OutputType {
output_location = arg;
}
Err(err) => {
streams.err.appendln(wgettext_fmt!(
streams.err.appendln(&wgettext_fmt!(
"Opening \"%s\" failed: %s",
arg,
err.to_string()
@@ -1168,7 +1168,7 @@ enum OutputType {
let _ = file.write_all(&wcs2bytes(&output_wtext));
}
Err(err) => {
streams.err.appendln(wgettext_fmt!(
streams.err.appendln(&wgettext_fmt!(
"Opening \"%s\" failed: %s",
output_location,
err.to_string()
@@ -1314,12 +1314,12 @@ fn prettify(streams: &mut IoStreams, src: &wstr, do_indent: bool) -> WString {
};
let ast = ast::parse(src, flags, None);
let ast_dump = ast.dump(src);
streams.err.appendln(ast_dump);
streams.err.appendln(&ast_dump);
// Output metrics too.
let mut metrics = AstSizeMetrics::default();
metrics.visit(ast.top());
streams.err.appendln(format!("{}", metrics));
streams.err.appendln(&format!("{}", metrics));
}
let ast = ast::parse(src, parse_flags(), None);
let mut printer = PrettyPrinter::new(src, &ast, do_indent);

View File

@@ -160,7 +160,7 @@ fn setup_and_process_keys(
.err
.appendln("To terminate this program type \"exit\" or \"quit\" in this window,");
let modes = shell_modes();
streams.err.appendln(wgettext_fmt!(
streams.err.appendln(&wgettext_fmt!(
"or press ctrl-%c or ctrl-%c twice in a row.",
char::from(modes.c_cc[VINTR] + 0x60),
char::from(modes.c_cc[VEOF] + 0x60)
@@ -201,7 +201,7 @@ fn parse_flags(
return ControlFlow::Break(Ok(SUCCESS));
}
'v' => {
streams.out.appendln(wgettext_fmt!(
streams.out.appendln(&wgettext_fmt!(
"%s, version %s",
get_program_name(),
crate::BUILD_VERSION
@@ -235,7 +235,7 @@ fn parse_flags(
if argc != 0 {
streams
.err
.appendln(wgettext_fmt!("Expected no arguments, got %d", argc));
.appendln(&wgettext_fmt!("Expected no arguments, got %d", argc));
return ControlFlow::Break(Err(STATUS_CMD_ERROR));
}

View File

@@ -208,7 +208,7 @@ pub fn functions(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -
} else {
L!("n/a").to_owned()
};
streams.out.appendln(def_file);
streams.out.appendln(&def_file);
if opts.verbose {
let copy_place = match props.as_ref() {
@@ -223,20 +223,20 @@ pub fn functions(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -
Some(p) if !p.is_autoload.load() => L!("not-autoloaded").to_owned(),
_ => L!("n/a").to_owned(),
};
streams.out.appendln(copy_place);
streams.out.appendln(&copy_place);
let line = if let Some(p) = props.as_ref() {
p.definition_lineno()
} else {
0
};
streams.out.appendln(line.to_wstring());
streams.out.appendln(&line.to_wstring());
let shadow = match props.as_ref() {
Some(p) if p.shadow_scope => L!("scope-shadowing").to_owned(),
Some(p) if !p.shadow_scope => L!("no-scope-shadowing").to_owned(),
_ => L!("n/a").to_owned(),
};
streams.out.appendln(shadow);
streams.out.appendln(&shadow);
let desc = match props.as_ref() {
Some(p) => {
@@ -254,7 +254,7 @@ pub fn functions(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -
}
_ => L!("n/a").to_owned(),
};
streams.out.appendln(desc);
streams.out.appendln(&desc);
}
// Historical - this never failed?
return Ok(SUCCESS);
@@ -297,7 +297,7 @@ pub fn functions(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -
.append(&reformat_for_screen(&buff, &termsize_last()));
} else {
for name in names {
streams.out.appendln(name);
streams.out.appendln(&name);
}
}
return Ok(SUCCESS);

View File

@@ -63,6 +63,6 @@ pub fn pwd(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Buil
if pwd.is_empty() {
return Err(STATUS_CMD_ERROR);
}
streams.out.appendln(pwd);
streams.out.appendln(&pwd);
Ok(SUCCESS)
}

View File

@@ -162,6 +162,6 @@ fn parse_ull(streams: &mut IoStreams, cmd: &wstr, num: &wstr) -> Result<u64, wut
// Safe because end was a valid i64 and the result here is in the range start..=end.
let result: i64 = start.checked_add_unsigned(rand * step).unwrap();
streams.out.appendln(result.to_wstring());
streams.out.appendln(&result.to_wstring());
Ok(SUCCESS)
}

View File

@@ -186,7 +186,7 @@ fn parse(
// implicit drop(w); here
if args[optind - 1].starts_with("-o") {
// TODO: translate this
streams.err.appendln(sprintf!(
streams.err.appendln(&sprintf!(
"Fish does not have shell options. See `help %s`.",
help_section!("fish_for_bash_users")
));

View File

@@ -562,7 +562,7 @@ pub fn status(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
return Err(STATUS_INVALID_ARGS);
}
if args[0] != "scroll-content-up" {
streams.err.appendln(wgettext_fmt!(
streams.err.appendln(&wgettext_fmt!(
"%s %s: unrecognized feature '%s'",
cmd,
c.to_wstr(),
@@ -602,18 +602,18 @@ pub fn status(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
};
streams.out.appendln(buildsystem);
streams.out.append(L!("Version: "));
streams.out.appendln(version);
streams.out.appendln(&version);
if target == host {
streams.out.append(L!("Target (and host): "));
streams.out.appendln(target);
streams.out.appendln(&target);
} else {
streams.out.append(L!("Target: "));
streams.out.appendln(target);
streams.out.appendln(&target);
streams.out.append(L!("Host: "));
streams.out.appendln(host);
streams.out.appendln(&host);
}
streams.out.append(L!("Profile: "));
streams.out.appendln(profile);
streams.out.appendln(&profile);
streams.out.append(L!("Features: "));
let features: &[&str] = &[
#[cfg(feature = "embed-manpages")]
@@ -623,7 +623,7 @@ pub fn status(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
#[cfg(target_feature = "crt-static")]
"crt-static",
];
streams.out.appendln(str2wcstring(features.join(" ")));
streams.out.appendln(&str2wcstring(features.join(" ")));
streams.out.appendln("");
return Ok(SUCCESS);
}
@@ -643,7 +643,7 @@ pub fn status(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
Some(f) => f,
None => wgettext!("Not a function").to_owned(),
};
streams.out.appendln(f);
streams.out.appendln(&f);
}
STATUS_LINE_NUMBER => {
// TBD is how to interpret the level argument when fetching the line number.
@@ -651,7 +651,7 @@ pub fn status(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
// streams.out.append_format(L"%d\n", parser.get_lineno(opts.level));
streams
.out
.appendln(parser.get_lineno_for_display().to_wstring());
.appendln(&parser.get_lineno_for_display().to_wstring());
}
STATUS_IS_INTERACTIVE => {
if is_interactive_session() {
@@ -745,7 +745,7 @@ pub fn status(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
}
LookUpInPath => Cow::Borrowed(get_program_name()),
};
streams.out.appendln(result);
streams.out.appendln(&result);
}
STATUS_TERMINAL => {
let xtversion = xtversion().unwrap_or_default();

View File

@@ -52,7 +52,7 @@ fn handle(
nnonempty += 1;
}
if !self.quiet {
streams.out.appendln(max.to_wstring());
streams.out.appendln(&max.to_wstring());
} else if nnonempty > 0 {
return Ok(());
}
@@ -63,7 +63,7 @@ fn handle(
nnonempty += 1;
}
if !self.quiet {
streams.out.appendln(n.to_wstring());
streams.out.appendln(&n.to_wstring());
} else if nnonempty > 0 {
return Ok(());
}

View File

@@ -82,7 +82,7 @@ fn handle(
// echo whatever
// end
for InputValue { arg, .. } in iter {
streams.out.appendln(arg);
streams.out.appendln(&arg);
}
return Ok(());
}
@@ -182,7 +182,7 @@ fn handle(
res
}
};
streams.out.appendln(output);
streams.out.appendln(&output);
continue;
} else {
/* shorten the right side */
@@ -221,7 +221,7 @@ fn handle(
}
if pos == line.len() {
streams.out.appendln(line);
streams.out.appendln(&line);
continue;
}

View File

@@ -1020,7 +1020,7 @@ pub fn test(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
if feature_test(FeatureFlag::TestRequireArg) {
if argc == 0 {
streams.err.appendln(wgettext_fmt!(
streams.err.appendln(&wgettext_fmt!(
"%s: Expected at least one argument",
program_name
));
@@ -1035,7 +1035,7 @@ pub fn test(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
}
} else if argc == 0 {
if should_flog!(deprecated_test) {
streams.err.appendln(wgettext_fmt!(
streams.err.appendln(&wgettext_fmt!(
"%s: called with no arguments. This will be an error in future.",
program_name
));
@@ -1044,7 +1044,7 @@ pub fn test(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
return Err(STATUS_INVALID_ARGS); // Per 1003.1, exit false.
} else if argc == 1 {
if should_flog!(deprecated_test) && args[0] != "-z" {
streams.err.appendln(wgettext_fmt!(
streams.err.appendln(&wgettext_fmt!(
"%s: called with one argument. This will return false in future.",
program_name
));

View File

@@ -690,12 +690,18 @@ pub fn append(&mut self, s: impl IntoCharIter) -> bool {
}
}
/// Append the given characters and a trailing newline.
pub fn appendln(&mut self, s: impl IntoCharIter) -> bool {
// Try calling "append" less - it might write() to an fd
self.append(s.chars().chain(std::iter::once('\n')))
}
/// An optional override point. This is for explicit separation.
/// \param want_newline this is true if the output item should be ended with a newline. This
/// is only relevant if we are printing the output to a stream,
pub fn append_with_separation(
&mut self,
s: &wstr,
s: impl IntoCharIter,
typ: SeparationType,
want_newline: bool,
) -> bool {
@@ -703,10 +709,7 @@ pub fn append_with_separation(
OutputStream::Buffered(stream) => stream.append_with_separation(s, typ, want_newline),
OutputStream::Fd(_) | OutputStream::Null | OutputStream::String(_) => {
if typ == SeparationType::explicitly && want_newline {
// Try calling "append" less - it might write() to an fd
let mut buf = s.to_owned();
buf.push('\n');
self.append(&buf)
self.appendln(s)
} else {
self.append(s)
}
@@ -714,12 +717,6 @@ pub fn append_with_separation(
}
}
/// Append a &wstr or WString with a newline
pub fn appendln(&mut self, s: impl Into<WString>) -> bool {
let s = s.into() + L!("\n");
self.append(&s)
}
pub fn append_char(&mut self, c: char) -> bool {
self.append(wstr::from_char_slice(&[c]))
}
@@ -840,7 +837,7 @@ fn append(&mut self, s: impl IntoCharIter) -> bool {
}
fn append_with_separation(
&mut self,
s: &wstr,
s: impl IntoCharIter,
typ: SeparationType,
_want_newline: bool,
) -> bool {