1
1
mirror of https://github.com/wez/wezterm.git synced 2024-12-28 07:55:03 +03:00

config: expand canonicalize_pasted_newlines

This setting now allows specifying what the canonical format is;
previously it was a boolean that meant "don't change anything"
if false, and rewrite to CRLF if true.

It's now an enum with None, CR, LF, and CRLF variants that express
more control.

The config accepts true (maps to CRLF) and false (maps to None)
for backwards compatibility.

The default behavior is unchanged by this commit, however, I did
uncover a bug in the canonicalizer for inputs like `\r\r\n`.

refs: #1575
This commit is contained in:
Wez Furlong 2022-01-22 09:46:27 -07:00
parent 78ea214a96
commit 8ee6739ece
4 changed files with 204 additions and 53 deletions

View File

@ -22,7 +22,7 @@ use crate::{
use anyhow::Context;
use luahelper::impl_lua_conversion;
use portable_pty::{CommandBuilder, PtySize};
use serde::{Deserialize, Serialize};
use serde::{Deserialize, Deserializer, Serialize};
use std::collections::HashMap;
use std::ffi::OsStr;
use std::io::Read;
@ -567,8 +567,8 @@ pub struct Config {
#[serde(default)]
pub audible_bell: AudibleBell,
#[serde(default = "default_canonicalize_pasted_newlines")]
pub canonicalize_pasted_newlines: bool,
#[serde(default)]
pub canonicalize_pasted_newlines: Option<NewlineCanon>,
#[serde(default = "default_unicode_version")]
pub unicode_version: u8,
@ -1136,10 +1136,6 @@ fn default_unicode_version() -> u8 {
9
}
fn default_canonicalize_pasted_newlines() -> bool {
cfg!(windows)
}
fn default_mux_env_remove() -> Vec<String> {
vec![
"SSH_AUTH_SOCK".to_string(),
@ -1274,6 +1270,66 @@ impl Default for WindowPadding {
}
}
#[derive(Serialize, Clone, Copy, Debug, PartialEq, Eq)]
pub enum NewlineCanon {
None,
LineFeed,
CarriageReturn,
CarriageReturnAndLineFeed,
}
impl_lua_conversion!(NewlineCanon);
impl<'de> Deserialize<'de> for NewlineCanon {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct Helper;
impl<'de> serde::de::Visitor<'de> for Helper {
type Value = NewlineCanon;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("true, false, \"None\", \"LineFeed\", \"CarriageReturnAndLineFeed\", \"CarriageReturnAndLineFeed\"")
}
fn visit_bool<E>(self, value: bool) -> Result<NewlineCanon, E>
where
E: serde::de::Error,
{
Ok(if value {
NewlineCanon::CarriageReturnAndLineFeed
} else {
NewlineCanon::LineFeed
})
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
match v {
"None" => Ok(NewlineCanon::None),
"LineFeed" => Ok(NewlineCanon::LineFeed),
"CarriageReturn" => Ok(NewlineCanon::CarriageReturn),
"CarriageReturnAndLineFeed" => Ok(NewlineCanon::CarriageReturnAndLineFeed),
_ => Err(serde::de::Error::unknown_variant(
v,
&[
"None",
"LineFeed",
"CarriageReturn",
"CarriageReturnAndLineFeed",
],
)),
}
}
}
deserializer.deserialize_any(Helper)
}
}
#[derive(Deserialize, Serialize, Clone, Copy, Debug)]
pub enum WindowCloseConfirmation {
AlwaysPrompt,

View File

@ -1,6 +1,6 @@
//! Bridge our gui config into the terminal crate configuration
use crate::{configuration, ConfigHandle};
use crate::{configuration, ConfigHandle, NewlineCanon};
use std::sync::Mutex;
use termwiz::hyperlink::Rule as HyperlinkRule;
use wezterm_term::color::ColorPalette;
@ -71,8 +71,18 @@ impl wezterm_term::TerminalConfiguration for TermConfig {
self.configuration().enable_kitty_graphics
}
fn canonicalize_pasted_newlines(&self) -> bool {
self.configuration().canonicalize_pasted_newlines
fn canonicalize_pasted_newlines(&self) -> wezterm_term::config::NewlineCanon {
match self.configuration().canonicalize_pasted_newlines {
None => wezterm_term::config::NewlineCanon::default(),
Some(NewlineCanon::None) => wezterm_term::config::NewlineCanon::None,
Some(NewlineCanon::LineFeed) => wezterm_term::config::NewlineCanon::LineFeed,
Some(NewlineCanon::CarriageReturn) => {
wezterm_term::config::NewlineCanon::CarriageReturn
}
Some(NewlineCanon::CarriageReturnAndLineFeed) => {
wezterm_term::config::NewlineCanon::CarriageReturnAndLineFeed
}
}
}
fn unicode_version(&self) -> u8 {

View File

@ -1,6 +1,124 @@
use crate::color::ColorPalette;
use termwiz::hyperlink::Rule as HyperlinkRule;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum NewlineCanon {
None,
LineFeed,
CarriageReturn,
CarriageReturnAndLineFeed,
}
impl NewlineCanon {
fn target(self) -> Option<&'static str> {
match self {
Self::None => None,
Self::LineFeed => Some("\n"),
Self::CarriageReturn => Some("\r"),
Self::CarriageReturnAndLineFeed => Some("\r\n"),
}
}
pub fn canonicalize(self, text: &str) -> String {
let target = self.target();
let mut buf = String::new();
let mut iter = text.chars().peekable();
while let Some(c) = iter.next() {
match target {
None => buf.push(c),
Some(canon) => {
if c == '\n' {
buf.push_str(canon);
} else if c == '\r' {
buf.push_str(canon);
if let Some('\n') = iter.peek() {
// Paired with the \r, so consume this one
iter.next();
}
} else {
buf.push(c);
}
}
}
}
buf
}
}
#[cfg(test)]
#[test]
fn test_canon() {
assert_eq!(
"hello\nthere",
NewlineCanon::None.canonicalize("hello\nthere")
);
assert_eq!(
"hello\r\nthere",
NewlineCanon::CarriageReturnAndLineFeed.canonicalize("hello\nthere")
);
assert_eq!(
"hello\rthere",
NewlineCanon::CarriageReturn.canonicalize("hello\nthere")
);
assert_eq!(
"hello\nthere",
NewlineCanon::LineFeed.canonicalize("hello\nthere")
);
assert_eq!(
"hello\nthere",
NewlineCanon::LineFeed.canonicalize("hello\r\nthere")
);
assert_eq!(
"hello\nthere",
NewlineCanon::LineFeed.canonicalize("hello\rthere")
);
assert_eq!(
"hello\n\nthere",
NewlineCanon::LineFeed.canonicalize("hello\r\rthere")
);
assert_eq!(
"hello\n\nthere",
NewlineCanon::LineFeed.canonicalize("hello\r\n\rthere")
);
assert_eq!(
"hello\n\nthere",
NewlineCanon::LineFeed.canonicalize("hello\r\n\nthere")
);
assert_eq!(
"hello\n\nthere",
NewlineCanon::LineFeed.canonicalize("hello\r\n\r\nthere")
);
assert_eq!(
"hello\n\n\nthere",
NewlineCanon::LineFeed.canonicalize("hello\r\r\n\nthere")
);
}
impl Default for NewlineCanon {
fn default() -> Self {
// This is a bit horrible; in general we try to stick with unix line
// endings as the one-true representation because using canonical
// CRLF can result in excess blank lines during a paste operation.
// On Windows we're in a bit of a frustrating situation: pasting into
// Windows console programs requires CRLF otherwise there is no newline
// at all, but when in WSL, pasting with CRLF gives excess blank lines.
//
// To come to a compromise, if wezterm is running on Windows then we'll
// use canonical CRLF unless the embedded application has enabled
// bracketed paste: we can use bracketed paste mode as a signal that
// the application will prefer newlines.
//
// In practice this means that unix shells and vim will get the
// unix newlines in their pastes (which is the UX I want) and
// cmd.exe will get CRLF.
if cfg!(windows) {
Self::CarriageReturnAndLineFeed
} else {
Self::None
}
}
}
/// TerminalConfiguration allows for the embedding application to pass configuration
/// information to the Terminal.
/// The configuration can be changed at runtime; provided that the implementation
@ -69,8 +187,8 @@ pub trait TerminalConfiguration: std::fmt::Debug {
cfg!(windows)
}
fn canonicalize_pasted_newlines(&self) -> bool {
cfg!(windows)
fn canonicalize_pasted_newlines(&self) -> NewlineCanon {
NewlineCanon::default()
}
fn alternate_buffer_wheel_scroll_speed(&self) -> u8 {

View File

@ -3,6 +3,7 @@
#![cfg_attr(feature = "cargo-clippy", allow(clippy::range_plus_one))]
use super::*;
use crate::color::{ColorPalette, RgbColor};
use crate::config::NewlineCanon;
use log::debug;
use num_traits::ToPrimitive;
use std::collections::HashMap;
@ -715,48 +716,14 @@ impl TerminalState {
buf.push_str("\x1b[200~");
}
// This is a bit horrible; in general we try to stick with unix line
// endings as the one-true representation because using canonical
// CRLF can result in excess blank lines during a paste operation.
// On Windows we're in a bit of a frustrating situation: pasting into
// Windows console programs requires CRLF otherwise there is no newline
// at all, but when in WSL, pasting with CRLF gives excess blank lines.
//
// To come to a compromise, if wezterm is running on Windows then we'll
// use canonical CRLF unless the embedded application has enabled
// bracketed paste: we can use bracketed paste mode as a signal that
// the application will prefer newlines.
//
// In practice this means that unix shells and vim will get the
// unix newlines in their pastes (which is the UX I want) and
// cmd.exe will get CRLF.
let canonicalize_line_endings =
self.config.canonicalize_pasted_newlines() && !self.bracketed_paste;
if canonicalize_line_endings {
// Convert (\r|\n) -> \r\n, but not if it is \r\n anyway.
let mut iter = text.chars();
while let Some(c) = iter.next() {
if c == '\n' {
buf.push_str("\r\n");
} else if c == '\r' {
buf.push_str("\r\n");
match iter.next() {
Some(c) if c == '\n' => {
// Already emitted it above
}
Some(c) => buf.push(c),
None => {
// No more text and we already emitted \r\n above
}
}
} else {
buf.push(c);
}
}
let canon = if self.bracketed_paste {
NewlineCanon::None
} else {
buf.push_str(text);
}
self.config.canonicalize_pasted_newlines()
};
let canon = canon.canonicalize(text);
buf.push_str(&canon);
if self.bracketed_paste {
buf.push_str("\x1b[201~");