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:
parent
78ea214a96
commit
8ee6739ece
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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~");
|
||||
|
Loading…
Reference in New Issue
Block a user