Repository: nushell/nushell (Rust)

Issue:
Prompt is bold by default
### Describe the bug

Set any prompt
```js
$env.PROMPT_COMMAND = {|| (ansi { fg: '#fafafa' attr: 'n'}) + $env.PWD }
```
and it's bold if you don't `ansi reset` at the start. Very confusing

### How to reproduce

1. Wish to make a non-bold prompt

### Expected behavior

Don't set any default attributes to user's prompt

### Screenshots

_No response_

### Configuration

| key                | value                                                             |
| ------------------ | ----------------------------------------------------------------- |
| version            | 0.78.0                                                            |
| branch             |                                                                   |
| commit_hash        |                                                                   |
| build_os           | linux-x86_64                                                      |
| build_target       | x86_64-unknown-linux-gnu                                          |
| rust_version       | rustc 1.68.2 (9eb3afe9e 2023-03-27) (built from a source tarball) |
| cargo_version      | cargo 1.68.1                                                      |
| build_time         | 1980-01-01 00:00:00 +00:00                                        |
| build_rust_channel | release                                                           |
| features           | default, zip                                                      |
| installed_plugins  |                                                                   |

### Additional context

0.79 `nu -n` same behaviour



Fix (patch):
diff --git a/src/cli.rs b/src/cli.rs
index 706b767024ce8..cfd9d8de6e0c1 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -210,6 +210,7 @@ pub async fn cli() -> Result<(), Box<dyn Error>> {
             per_item_command(Open),
             per_item_command(Post),
             per_item_command(Where),
+            per_item_command(Echo),
             whole_stream_command(Config),
             whole_stream_command(SkipWhile),
             per_item_command(Enter),
diff --git a/src/commands.rs b/src/commands.rs
index e0e1c80e5abd0..f2885f369fc39 100644
--- a/src/commands.rs
+++ b/src/commands.rs
@@ -11,6 +11,7 @@ pub(crate) mod config;
 pub(crate) mod cp;
 pub(crate) mod date;
 pub(crate) mod debug;
+pub(crate) mod echo;
 pub(crate) mod enter;
 pub(crate) mod exit;
 pub(crate) mod fetch;
@@ -76,6 +77,7 @@ pub(crate) use config::Config;
 pub(crate) use cp::Cpy;
 pub(crate) use date::Date;
 pub(crate) use debug::Debug;
+pub(crate) use echo::Echo;
 pub(crate) use enter::Enter;
 pub(crate) use exit::Exit;
 pub(crate) use fetch::Fetch;
diff --git a/src/commands/autoview.rs b/src/commands/autoview.rs
index f970d23e4f621..adce1df4021a3 100644
--- a/src/commands/autoview.rs
+++ b/src/commands/autoview.rs
@@ -58,7 +58,7 @@ pub fn autoview(
                         }
                     }
                 };
-            } else if is_single_text_value(&input) {
+            } else if is_single_origined_text_value(&input) {
                 let text = context.get_command("textview");
                 if let Some(text) = text {
                     let result = text.run(raw.with_input(input), &context.commands);
@@ -73,6 +73,15 @@ pub fn autoview(
                         }
                     }
                 }
+            } else if is_single_text_value(&input) {
+                for i in input {
+                    match i.item {
+                        Value::Primitive(Primitive::String(s)) => {
+                            println!("{}", s);
+                        }
+                        _ => {}
+                    }
+                }
             } else {
                 let table = context.expect_command("table");
                 let result = table.run(raw.with_input(input), &context.commands);
@@ -96,3 +105,20 @@ fn is_single_text_value(input: &Vec<Tagged<Value>>) -> bool {
         false
     }
 }
+
+fn is_single_origined_text_value(input: &Vec<Tagged<Value>>) -> bool {
+    if input.len() != 1 {
+        return false;
+    }
+    if let Tagged {
+        item: Value::Primitive(Primitive::String(_)),
+        tag: Tag {
+            origin: Some(_), ..
+        },
+    } = input[0]
+    {
+        true
+    } else {
+        false
+    }
+}
diff --git a/src/commands/classified.rs b/src/commands/classified.rs
index 959a8f6125b31..277a9253416a4 100644
--- a/src/commands/classified.rs
+++ b/src/commands/classified.rs
@@ -220,111 +220,60 @@ impl ExternalCommand {
 
         let mut process;
 
-        #[cfg(windows)]
-        {
-            process = Exec::shell(&self.name);
-
-            if arg_string.contains("$it") {
-                let mut first = true;
-
-                for i in &inputs {
-                    if i.as_string().is_err() {
-                        let mut span = None;
-                        for arg in &self.args {
-                            if arg.item.contains("$it") {
-                                span = Some(arg.span());
-                            }
-                        }
-                        if let Some(span) = span {
-                            return Err(ShellError::labeled_error(
-                                "External $it needs string data",
-                                "given row instead of string data",
-                                span,
-                            ));
-                        } else {
-                            return Err(ShellError::string("Error: $it needs string data"));
-                        }
-                    }
-                    if !first {
-                        process = process.arg("&&");
-                        process = process.arg(&self.name);
-                    } else {
-                        first = false;
-                    }
+        process = Exec::cmd(&self.name);
+
+        if arg_string.contains("$it") {
+            let mut first = true;
 
+            for i in &inputs {
+                if i.as_string().is_err() {
+                    let mut span = None;
                     for arg in &self.args {
-                        if arg.chars().all(|c| c.is_whitespace()) {
-                            continue;
+                        if arg.item.contains("$it") {
+                            span = Some(arg.span());
                         }
-
-                        process = process.arg(&arg.replace("$it", &i.as_string()?));
                     }
-                }
-            } else {
-                for arg in &self.args {
-                    let arg_chars: Vec<_> = arg.chars().collect();
-                    if arg_chars.len() > 1
-                        && arg_chars[0] == '"'
-                        && arg_chars[arg_chars.len() - 1] == '"'
-                    {
-                        // quoted string
-                        let new_arg: String = arg_chars[1..arg_chars.len() - 1].iter().collect();
-                        process = process.arg(new_arg);
+                    if let Some(span) = span {
+                        return Err(ShellError::labeled_error(
+                            "External $it needs string data",
+                            "given row instead of string data",
+                            span,
+                        ));
                     } else {
-                        process = process.arg(arg.item.clone());
+                        return Err(ShellError::string("Error: $it needs string data"));
                     }
                 }
-            }
-        }
-        #[cfg(not(windows))]
-        {
-            let mut new_arg_string = self.name.to_string();
-
-            if arg_string.contains("$it") {
-                let mut first = true;
-                for i in &inputs {
-                    let i = match i.as_string() {
-                        Err(_err) => {
-                            let mut span = name_span;
-                            for arg in &self.args {
-                                if arg.item.contains("$it") {
-                                    span = arg.span();
-                                }
-                            }
-                            return Err(ShellError::labeled_error(
-                                "External $it needs string data",
-                                "given row instead of string data",
-                                span,
-                            ));
-                        }
-                        Ok(val) => val,
-                    };
+                if !first {
+                    process = process.arg("&&");
+                    process = process.arg(&self.name);
+                } else {
+                    first = false;
+                }
 
-                    if !first {
-                        new_arg_string.push_str("&&");
-                        new_arg_string.push_str(&self.name);
-                    } else {
-                        first = false;
+                for arg in &self.args {
+                    if arg.chars().all(|c| c.is_whitespace()) {
+                        continue;
                     }
 
-                    for arg in &self.args {
-                        if arg.chars().all(|c| c.is_whitespace()) {
-                            continue;
-                        }
-
-                        new_arg_string.push_str(" ");
-                        new_arg_string.push_str(&arg.replace("$it", &i));
-                    }
+                    process = process.arg(&arg.replace("$it", &i.as_string()?));
                 }
-            } else {
-                for arg in &self.args {
-                    new_arg_string.push_str(" ");
-                    new_arg_string.push_str(&arg);
+            }
+        } else {
+            for arg in &self.args {
+                let arg_chars: Vec<_> = arg.chars().collect();
+                if arg_chars.len() > 1
+                    && arg_chars[0] == '"'
+                    && arg_chars[arg_chars.len() - 1] == '"'
+                {
+                    // quoted string
+                    let new_arg: String = arg_chars[1..arg_chars.len() - 1].iter().collect();
+                    process = process.arg(new_arg);
+                } else {
+                    process = process.arg(arg.item.clone());
                 }
             }
-
-            process = Exec::shell(new_arg_string);
         }
+
         process = process.cwd(context.shell_manager.path());
 
         let mut process = match stream_next {
diff --git a/src/commands/echo.rs b/src/commands/echo.rs
new file mode 100644
index 0000000000000..f464630de7d43
--- /dev/null
+++ b/src/commands/echo.rs
@@ -0,0 +1,72 @@
+use crate::data::Value;
+use crate::errors::ShellError;
+use crate::prelude::*;
+
+use crate::parser::registry::Signature;
+
+pub struct Echo;
+
+impl PerItemCommand for Echo {
+    fn name(&self) -> &str {
+        "echo"
+    }
+
+    fn signature(&self) -> Signature {
+        Signature::build("echo").rest(SyntaxType::Any)
+    }
+
+    fn usage(&self) -> &str {
+        "Echo the argments back to the user."
+    }
+
+    fn run(
+        &self,
+        call_info: &CallInfo,
+        registry: &CommandRegistry,
+        raw_args: &RawCommandArgs,
+        _input: Tagged<Value>,
+    ) -> Result<OutputStream, ShellError> {
+        run(call_info, registry, raw_args)
+    }
+}
+
+fn run(
+    call_info: &CallInfo,
+    _registry: &CommandRegistry,
+    _raw_args: &RawCommandArgs,
+) -> Result<OutputStream, ShellError> {
+    let name = call_info.name_span;
+
+    let mut output = String::new();
+
+    let mut first = true;
+
+    if let Some(ref positional) = call_info.args.positional {
+        for i in positional {
+            match i.as_string() {
+                Ok(s) => {
+                    if !first {
+                        output.push_str(" ");
+                    } else {
+                        first = false;
+                    }
+
+                    output.push_str(&s);
+                }
+                _ => {
+                    return Err(ShellError::labeled_error(
+                        "Expect a string from pipeline",
+                        "not a string-compatible value",
+                        i.span(),
+                    ));
+                }
+            }
+        }
+    }
+
+    let stream = VecDeque::from(vec![Ok(ReturnSuccess::Value(
+        Value::string(output).simple_spanned(name),
+    ))]);
+
+    Ok(stream.to_output_stream())
+}