mirror of
https://github.com/wez/wezterm.git
synced 2024-12-22 21:01:36 +03:00
introduce unicode_version config
This is a fairly far-reaching commit. The idea is: * Introduce a unicode_version config that specifies the default level of unicode conformance for each newly created Terminal (each Pane) * The unicode_version is passed down to the `grapheme_column_width` function which interprets the width based on the version * `Cell` records the width so that later calculations don't need to know the unicode version In a subsequent diff, I will introduce an escape sequence that allows setting/pushing/popping the unicode version so that it can be overridden via eg: a shell alias prior to launching an application that uses a different version of unicode from the default. This approach allows output from multiple applications with differing understanding of unicode to coexist on the same screen a little more sanely. Note that the default `unicode_version` is set to 9, which means that emoji presentation selectors are now by-default ignored. This was selected to better match the level of support in widely deployed applications. I expect to raise that default version in the future. Also worth noting: there are a number of callers of `unicode_column_width` in things like overlays and lua helper functions that pass `None` for the unicode version: these will assume the latest known-to-wezterm/termwiz version of unicode to be desired. If those overlays do things with emoji presentation selectors, then there may be some alignment artifacts. That can be tackled in a follow up commit. refs: #1231 refs: #997
This commit is contained in:
parent
591e1f593c
commit
225e7a1243
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -4023,7 +4023,7 @@ checksum = "13a4ec180a2de59b57434704ccfad967f789b12737738798fa08798cd5824c16"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "termwiz"
|
name = "termwiz"
|
||||||
version = "0.13.0"
|
version = "0.14.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64",
|
"base64",
|
||||||
|
@ -1249,9 +1249,17 @@ pub struct Config {
|
|||||||
|
|
||||||
#[serde(default = "default_canonicalize_pasted_newlines")]
|
#[serde(default = "default_canonicalize_pasted_newlines")]
|
||||||
pub canonicalize_pasted_newlines: bool,
|
pub canonicalize_pasted_newlines: bool,
|
||||||
|
|
||||||
|
#[serde(default = "default_unicode_version")]
|
||||||
|
pub unicode_version: u8,
|
||||||
}
|
}
|
||||||
impl_lua_conversion!(Config);
|
impl_lua_conversion!(Config);
|
||||||
|
|
||||||
|
// Coupled with term/src/config.rs:TerminalConfiguration::unicode_version
|
||||||
|
fn default_unicode_version() -> u8 {
|
||||||
|
9
|
||||||
|
}
|
||||||
|
|
||||||
fn default_canonicalize_pasted_newlines() -> bool {
|
fn default_canonicalize_pasted_newlines() -> bool {
|
||||||
cfg!(windows)
|
cfg!(windows)
|
||||||
}
|
}
|
||||||
|
@ -164,13 +164,13 @@ pub fn make_lua_context(config_file: &Path) -> anyhow::Result<Lua> {
|
|||||||
|
|
||||||
wezterm_mod.set(
|
wezterm_mod.set(
|
||||||
"column_width",
|
"column_width",
|
||||||
lua.create_function(|_, s: String| Ok(unicode_column_width(&s)))?,
|
lua.create_function(|_, s: String| Ok(unicode_column_width(&s, None)))?,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
wezterm_mod.set(
|
wezterm_mod.set(
|
||||||
"pad_right",
|
"pad_right",
|
||||||
lua.create_function(|_, (mut result, width): (String, usize)| {
|
lua.create_function(|_, (mut result, width): (String, usize)| {
|
||||||
let mut len = unicode_column_width(&result);
|
let mut len = unicode_column_width(&result, None);
|
||||||
while len < width {
|
while len < width {
|
||||||
result.push(' ');
|
result.push(' ');
|
||||||
len += 1;
|
len += 1;
|
||||||
@ -183,7 +183,7 @@ pub fn make_lua_context(config_file: &Path) -> anyhow::Result<Lua> {
|
|||||||
wezterm_mod.set(
|
wezterm_mod.set(
|
||||||
"pad_left",
|
"pad_left",
|
||||||
lua.create_function(|_, (mut result, width): (String, usize)| {
|
lua.create_function(|_, (mut result, width): (String, usize)| {
|
||||||
let mut len = unicode_column_width(&result);
|
let mut len = unicode_column_width(&result, None);
|
||||||
while len < width {
|
while len < width {
|
||||||
result.insert(0, ' ');
|
result.insert(0, ' ');
|
||||||
len += 1;
|
len += 1;
|
||||||
@ -199,7 +199,7 @@ pub fn make_lua_context(config_file: &Path) -> anyhow::Result<Lua> {
|
|||||||
let mut result = String::new();
|
let mut result = String::new();
|
||||||
let mut len = 0;
|
let mut len = 0;
|
||||||
for g in s.graphemes(true) {
|
for g in s.graphemes(true) {
|
||||||
let g_len = grapheme_column_width(g);
|
let g_len = grapheme_column_width(g, None);
|
||||||
if g_len + len > max_width {
|
if g_len + len > max_width {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -217,7 +217,7 @@ pub fn make_lua_context(config_file: &Path) -> anyhow::Result<Lua> {
|
|||||||
let mut result = vec![];
|
let mut result = vec![];
|
||||||
let mut len = 0;
|
let mut len = 0;
|
||||||
for g in s.graphemes(true).rev() {
|
for g in s.graphemes(true).rev() {
|
||||||
let g_len = grapheme_column_width(g);
|
let g_len = grapheme_column_width(g, None);
|
||||||
if g_len + len > max_width {
|
if g_len + len > max_width {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -74,4 +74,8 @@ impl wezterm_term::TerminalConfiguration for TermConfig {
|
|||||||
fn canonicalize_pasted_newlines(&self) -> bool {
|
fn canonicalize_pasted_newlines(&self) -> bool {
|
||||||
self.configuration().canonicalize_pasted_newlines
|
self.configuration().canonicalize_pasted_newlines
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn unicode_version(&self) -> u8 {
|
||||||
|
self.configuration().unicode_version
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,12 +25,15 @@ impl LineEditorHost for PasswordPromptHost {
|
|||||||
// characters when output to the terminal widget
|
// characters when output to the terminal widget
|
||||||
fn highlight_line(&self, line: &str, cursor_position: usize) -> (Vec<OutputElement>, usize) {
|
fn highlight_line(&self, line: &str, cursor_position: usize) -> (Vec<OutputElement>, usize) {
|
||||||
let placeholder = "🔑";
|
let placeholder = "🔑";
|
||||||
let grapheme_count = unicode_column_width(line);
|
let grapheme_count = unicode_column_width(line, None);
|
||||||
let mut output = vec![];
|
let mut output = vec![];
|
||||||
for _ in 0..grapheme_count {
|
for _ in 0..grapheme_count {
|
||||||
output.push(OutputElement::Text(placeholder.to_string()));
|
output.push(OutputElement::Text(placeholder.to_string()));
|
||||||
}
|
}
|
||||||
(output, unicode_column_width(placeholder) * cursor_position)
|
(
|
||||||
|
output,
|
||||||
|
unicode_column_width(placeholder, None) * cursor_position,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,12 +43,15 @@ impl LineEditorHost for PasswordPromptHost {
|
|||||||
// Rewrite the input so that we can obscure the password
|
// Rewrite the input so that we can obscure the password
|
||||||
// characters when output to the terminal widget
|
// characters when output to the terminal widget
|
||||||
let placeholder = "🔑";
|
let placeholder = "🔑";
|
||||||
let grapheme_count = unicode_column_width(line);
|
let grapheme_count = unicode_column_width(line, None);
|
||||||
let mut output = vec![];
|
let mut output = vec![];
|
||||||
for _ in 0..grapheme_count {
|
for _ in 0..grapheme_count {
|
||||||
output.push(OutputElement::Text(placeholder.to_string()));
|
output.push(OutputElement::Text(placeholder.to_string()));
|
||||||
}
|
}
|
||||||
(output, unicode_column_width(placeholder) * cursor_position)
|
(
|
||||||
|
output,
|
||||||
|
unicode_column_width(placeholder, None) * cursor_position,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,4 +9,4 @@ license = "MIT"
|
|||||||
documentation = "https://docs.rs/tabout"
|
documentation = "https://docs.rs/tabout"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
termwiz = { path = "../termwiz", version="0.13"}
|
termwiz = { path = "../termwiz", version="0.14"}
|
||||||
|
@ -28,7 +28,7 @@ fn emit_column<W: std::io::Write>(
|
|||||||
alignment: Alignment,
|
alignment: Alignment,
|
||||||
output: &mut W,
|
output: &mut W,
|
||||||
) -> Result<(), std::io::Error> {
|
) -> Result<(), std::io::Error> {
|
||||||
let text_width = unicode_column_width(text);
|
let text_width = unicode_column_width(text, None);
|
||||||
let (left_pad, right_pad) = match alignment {
|
let (left_pad, right_pad) = match alignment {
|
||||||
Alignment::Left => (0, max_width - text_width),
|
Alignment::Left => (0, max_width - text_width),
|
||||||
Alignment::Center => {
|
Alignment::Center => {
|
||||||
@ -66,14 +66,14 @@ pub fn tabulate_output<S: std::string::ToString, W: std::io::Write>(
|
|||||||
) -> Result<(), std::io::Error> {
|
) -> Result<(), std::io::Error> {
|
||||||
let mut col_widths: Vec<usize> = columns
|
let mut col_widths: Vec<usize> = columns
|
||||||
.iter()
|
.iter()
|
||||||
.map(|c| unicode_column_width(&c.name))
|
.map(|c| unicode_column_width(&c.name, None))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let mut display_rows: Vec<Vec<String>> = vec![];
|
let mut display_rows: Vec<Vec<String>> = vec![];
|
||||||
for src_row in rows {
|
for src_row in rows {
|
||||||
let dest_row: Vec<String> = src_row.iter().map(|col| col.to_string()).collect();
|
let dest_row: Vec<String> = src_row.iter().map(|col| col.to_string()).collect();
|
||||||
for (idx, col) in dest_row.iter().enumerate() {
|
for (idx, col) in dest_row.iter().enumerate() {
|
||||||
let col_width = unicode_column_width(col);
|
let col_width = unicode_column_width(col, None);
|
||||||
if let Some(width) = col_widths.get_mut(idx) {
|
if let Some(width) = col_widths.get_mut(idx) {
|
||||||
*width = (*width).max(col_width);
|
*width = (*width).max(col_width);
|
||||||
} else {
|
} else {
|
||||||
@ -116,7 +116,7 @@ pub fn unicode_column_width_of_change_slice(s: &[Change]) -> usize {
|
|||||||
s.iter()
|
s.iter()
|
||||||
.map(|c| {
|
.map(|c| {
|
||||||
if c.is_text() {
|
if c.is_text() {
|
||||||
unicode_column_width(c.text())
|
unicode_column_width(c.text(), None)
|
||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
@ -170,7 +170,7 @@ pub fn tabulate_for_terminal(
|
|||||||
) {
|
) {
|
||||||
let mut col_widths: Vec<usize> = columns
|
let mut col_widths: Vec<usize> = columns
|
||||||
.iter()
|
.iter()
|
||||||
.map(|c| unicode_column_width(&c.name))
|
.map(|c| unicode_column_width(&c.name, None))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
for row in rows {
|
for row in rows {
|
||||||
|
@ -37,6 +37,6 @@ pretty_env_logger = "0.4"
|
|||||||
k9 = "0.11.0"
|
k9 = "0.11.0"
|
||||||
|
|
||||||
[dependencies.termwiz]
|
[dependencies.termwiz]
|
||||||
version = "0.13"
|
version = "0.14"
|
||||||
path = "../termwiz"
|
path = "../termwiz"
|
||||||
features = ["use_image"]
|
features = ["use_image"]
|
||||||
|
@ -84,4 +84,15 @@ pub trait TerminalConfiguration: std::fmt::Debug {
|
|||||||
fn enable_kitty_graphics(&self) -> bool {
|
fn enable_kitty_graphics(&self) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The default unicode version to assume.
|
||||||
|
/// This affects how the width of certain sequences is interpreted.
|
||||||
|
/// At the time of writing, we default to 9 even though the current
|
||||||
|
/// version of unicode is 14. 14 introduced emoji presentation selectors
|
||||||
|
/// that also alter the width of certain sequences, and that is too
|
||||||
|
/// new for most deployed applications.
|
||||||
|
// Coupled with config/src/lib.rs:default_unicode_version
|
||||||
|
fn unicode_version(&self) -> u8 {
|
||||||
|
9
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ use std::collections::HashMap;
|
|||||||
use std::sync::mpsc::{channel, Sender};
|
use std::sync::mpsc::{channel, Sender};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use terminfo::{Database, Value};
|
use terminfo::{Database, Value};
|
||||||
|
use termwiz::cell::UnicodeVersion;
|
||||||
use termwiz::escape::csi::{
|
use termwiz::escape::csi::{
|
||||||
Cursor, CursorStyle, DecPrivateMode, DecPrivateModeCode, Device, Edit, EraseInDisplay,
|
Cursor, CursorStyle, DecPrivateMode, DecPrivateModeCode, Device, Edit, EraseInDisplay,
|
||||||
EraseInLine, Mode, Sgr, TabulationClear, TerminalMode, TerminalModeCode, Window, XtSmGraphics,
|
EraseInLine, Mode, Sgr, TabulationClear, TerminalMode, TerminalModeCode, Window, XtSmGraphics,
|
||||||
@ -341,6 +342,9 @@ pub struct TerminalState {
|
|||||||
|
|
||||||
kitty_img: KittyImageState,
|
kitty_img: KittyImageState,
|
||||||
seqno: SequenceNo,
|
seqno: SequenceNo,
|
||||||
|
|
||||||
|
/// The unicode version that is in effect
|
||||||
|
unicode_version: UnicodeVersion,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_color_map() -> HashMap<u16, RgbColor> {
|
fn default_color_map() -> HashMap<u16, RgbColor> {
|
||||||
@ -414,6 +418,8 @@ impl TerminalState {
|
|||||||
|
|
||||||
let color_map = default_color_map();
|
let color_map = default_color_map();
|
||||||
|
|
||||||
|
let unicode_version = UnicodeVersion(config.unicode_version());
|
||||||
|
|
||||||
TerminalState {
|
TerminalState {
|
||||||
config,
|
config,
|
||||||
screen,
|
screen,
|
||||||
@ -467,6 +473,7 @@ impl TerminalState {
|
|||||||
user_vars: HashMap::new(),
|
user_vars: HashMap::new(),
|
||||||
kitty_img: Default::default(),
|
kitty_img: Default::default(),
|
||||||
seqno: 0,
|
seqno: 0,
|
||||||
|
unicode_version,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,7 +134,7 @@ impl<'a> Performer<'a> {
|
|||||||
// they occupy a cell so that we can re-emit them when we output them.
|
// they occupy a cell so that we can re-emit them when we output them.
|
||||||
// If we didn't do this, then we'd effectively filter them out from
|
// If we didn't do this, then we'd effectively filter them out from
|
||||||
// the model, which seems like a lossy design choice.
|
// the model, which seems like a lossy design choice.
|
||||||
let print_width = grapheme_column_width(g).max(1);
|
let print_width = grapheme_column_width(g, Some(self.unicode_version)).max(1);
|
||||||
let wrappable = x + print_width >= width;
|
let wrappable = x + print_width >= width;
|
||||||
|
|
||||||
let cell = Cell::new_grapheme_with_width(g, print_width, pen);
|
let cell = Cell::new_grapheme_with_width(g, print_width, pen);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
authors = ["Wez Furlong"]
|
authors = ["Wez Furlong"]
|
||||||
name = "termwiz"
|
name = "termwiz"
|
||||||
version = "0.13.0"
|
version = "0.14.0"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
repository = "https://github.com/wez/wezterm"
|
repository = "https://github.com/wez/wezterm"
|
||||||
description = "Terminal Wizardry for Unix and Windows"
|
description = "Terminal Wizardry for Unix and Windows"
|
||||||
|
@ -517,6 +517,11 @@ where
|
|||||||
/// has length 2, otherwise, it has length 1 (we don't allow zero-length
|
/// has length 2, otherwise, it has length 1 (we don't allow zero-length
|
||||||
/// strings).
|
/// strings).
|
||||||
struct TeenyString(usize);
|
struct TeenyString(usize);
|
||||||
|
struct TeenyStringHeap {
|
||||||
|
bytes: Vec<u8>,
|
||||||
|
width: usize,
|
||||||
|
}
|
||||||
|
|
||||||
impl TeenyString {
|
impl TeenyString {
|
||||||
const fn marker_mask() -> usize {
|
const fn marker_mask() -> usize {
|
||||||
if cfg!(target_endian = "little") {
|
if cfg!(target_endian = "little") {
|
||||||
@ -591,9 +596,9 @@ impl TeenyString {
|
|||||||
|
|
||||||
let bytes = s.as_bytes();
|
let bytes = s.as_bytes();
|
||||||
let len = bytes.len();
|
let len = bytes.len();
|
||||||
|
let width = width.unwrap_or_else(|| grapheme_column_width(s, None));
|
||||||
|
|
||||||
if len < std::mem::size_of::<usize>() {
|
if len < std::mem::size_of::<usize>() {
|
||||||
let width = width.unwrap_or_else(|| grapheme_column_width(s));
|
|
||||||
debug_assert!(width < 3);
|
debug_assert!(width < 3);
|
||||||
|
|
||||||
let mut word = 0usize;
|
let mut word = 0usize;
|
||||||
@ -607,7 +612,10 @@ impl TeenyString {
|
|||||||
let word = Self::set_marker_bit(word, width);
|
let word = Self::set_marker_bit(word, width);
|
||||||
Self(word)
|
Self(word)
|
||||||
} else {
|
} else {
|
||||||
let vec = Box::new(bytes.to_vec());
|
let vec = Box::new(TeenyStringHeap {
|
||||||
|
bytes: bytes.to_vec(),
|
||||||
|
width,
|
||||||
|
});
|
||||||
let ptr = Box::into_raw(vec);
|
let ptr = Box::into_raw(vec);
|
||||||
Self(ptr as usize)
|
Self(ptr as usize)
|
||||||
}
|
}
|
||||||
@ -654,7 +662,8 @@ impl TeenyString {
|
|||||||
1
|
1
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
grapheme_column_width(self.str())
|
let heap = self.0 as *const usize as *const TeenyStringHeap;
|
||||||
|
unsafe { (*heap).width }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -676,8 +685,8 @@ impl TeenyString {
|
|||||||
|
|
||||||
&bytes[0..len]
|
&bytes[0..len]
|
||||||
} else {
|
} else {
|
||||||
let vec = self.0 as *const usize as *const Vec<u8>;
|
let heap = self.0 as *const usize as *const TeenyStringHeap;
|
||||||
unsafe { (*vec).as_slice() }
|
unsafe { (*heap).bytes.as_slice() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -819,26 +828,77 @@ impl Cell {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub struct UnicodeVersion(pub u8);
|
||||||
|
|
||||||
|
pub const LATEST_UNICODE_VERSION: UnicodeVersion = UnicodeVersion(14);
|
||||||
|
|
||||||
/// Returns the number of cells visually occupied by a sequence
|
/// Returns the number of cells visually occupied by a sequence
|
||||||
/// of graphemes
|
/// of graphemes.
|
||||||
pub fn unicode_column_width(s: &str) -> usize {
|
/// Calls through to `grapheme_column_width` for each grapheme
|
||||||
|
/// and sums up the length.
|
||||||
|
pub fn unicode_column_width(s: &str, version: Option<UnicodeVersion>) -> usize {
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
s.graphemes(true).map(grapheme_column_width).sum()
|
s.graphemes(true)
|
||||||
|
.map(|g| grapheme_column_width(g, version))
|
||||||
|
.sum()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the number of cells visually occupied by a grapheme.
|
/// Returns the number of cells visually occupied by a grapheme.
|
||||||
/// The input string must be a single grapheme.
|
/// The input string must be a single grapheme.
|
||||||
pub fn grapheme_column_width(s: &str) -> usize {
|
///
|
||||||
let width = s
|
/// There are some frustrating dragons in the realm of terminal cell widths:
|
||||||
|
///
|
||||||
|
/// a) wcwidth and wcswidth are widely used by applications and may be
|
||||||
|
/// several versions of unicode behind the current version
|
||||||
|
/// b) The width of characters has and will change in the future.
|
||||||
|
/// Unicode Version 8 -> 9 made some characters wider.
|
||||||
|
/// Unicode 14 defines Emoji variation selectors that change the
|
||||||
|
/// width depending on trailing context in the unicode sequence.
|
||||||
|
///
|
||||||
|
/// Differing opinions about the width leads to visual artifacts in
|
||||||
|
/// text and and line editors, especially with respect to cursor placement.
|
||||||
|
///
|
||||||
|
/// There aren't any really great solutions to this problem, as a given
|
||||||
|
/// terminal emulator may be fine locally but essentially breaks when
|
||||||
|
/// ssh'ing into a remote system with a divergent wcwidth implementation.
|
||||||
|
///
|
||||||
|
/// This means that a global understanding of the unicode version that
|
||||||
|
/// is in use isn't a good solution.
|
||||||
|
///
|
||||||
|
/// The approach that wezterm wants to take here is to define a
|
||||||
|
/// configuration value that sets the starting level of unicode conformance,
|
||||||
|
/// and to define an escape sequence that can push/pop a desired confirmance
|
||||||
|
/// level onto a stack maintained by the terminal emulator.
|
||||||
|
///
|
||||||
|
/// The terminal emulator can then pass the unicode version through to
|
||||||
|
/// the Cell that is used to hold a grapheme, and that per-Cell version
|
||||||
|
/// can then be used to calculate width.
|
||||||
|
pub fn grapheme_column_width(s: &str, version: Option<UnicodeVersion>) -> usize {
|
||||||
|
let version = version.unwrap_or(LATEST_UNICODE_VERSION).0;
|
||||||
|
|
||||||
|
let width: usize = s
|
||||||
.chars()
|
.chars()
|
||||||
.map(|c| WcWidth::from_char(c).width_unicode_9_or_later())
|
.map(|c| {
|
||||||
.max()
|
let c = WcWidth::from_char(c);
|
||||||
.unwrap_or(0);
|
if version >= 9 {
|
||||||
match Presentation::for_grapheme(s) {
|
c.width_unicode_9_or_later()
|
||||||
(_, Some(Presentation::Emoji)) => 2,
|
} else {
|
||||||
(_, Some(Presentation::Text)) => 1,
|
c.width_unicode_8_or_earlier()
|
||||||
(Presentation::Emoji, None) => 2,
|
}
|
||||||
(Presentation::Text, None) => width.into(),
|
})
|
||||||
|
.sum::<u8>()
|
||||||
|
.into();
|
||||||
|
|
||||||
|
if version >= 14 {
|
||||||
|
match Presentation::for_grapheme(s) {
|
||||||
|
(_, Some(Presentation::Emoji)) => 2,
|
||||||
|
(_, Some(Presentation::Text)) => 1,
|
||||||
|
(Presentation::Emoji, None) => 2,
|
||||||
|
(Presentation::Text, None) => width,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
width
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -911,7 +971,7 @@ mod test {
|
|||||||
for c in foot.chars() {
|
for c in foot.chars() {
|
||||||
eprintln!("char: {:?}", c);
|
eprintln!("char: {:?}", c);
|
||||||
}
|
}
|
||||||
assert_eq!(unicode_column_width(foot), 2, "{} should be 2", foot);
|
assert_eq!(unicode_column_width(foot, None), 2, "{} should be 2", foot);
|
||||||
|
|
||||||
let women_holding_hands_dark_skin_tone_medium_light_skin_tone =
|
let women_holding_hands_dark_skin_tone_medium_light_skin_tone =
|
||||||
"\u{1F469}\u{1F3FF}\u{200D}\u{1F91D}\u{200D}\u{1F469}\u{1F3FC}";
|
"\u{1F469}\u{1F3FF}\u{200D}\u{1F91D}\u{200D}\u{1F469}\u{1F3FC}";
|
||||||
@ -938,18 +998,31 @@ mod test {
|
|||||||
for c in deaf_man.chars() {
|
for c in deaf_man.chars() {
|
||||||
eprintln!("char: {:?}", c);
|
eprintln!("char: {:?}", c);
|
||||||
}
|
}
|
||||||
assert_eq!(unicode_column_width(deaf_man), 2);
|
assert_eq!(unicode_column_width(deaf_man, None), 2);
|
||||||
|
|
||||||
|
let man_dancing = "\u{1F57A}";
|
||||||
|
assert_eq!(
|
||||||
|
unicode_column_width(man_dancing, Some(UnicodeVersion(9))),
|
||||||
|
2
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unicode_column_width(man_dancing, Some(UnicodeVersion(8))),
|
||||||
|
1
|
||||||
|
);
|
||||||
|
|
||||||
// This is a codepoint in the private use area
|
// This is a codepoint in the private use area
|
||||||
let font_awesome_star = "\u{f005}";
|
let font_awesome_star = "\u{f005}";
|
||||||
eprintln!("font_awesome_star {}", font_awesome_star.escape_debug());
|
eprintln!("font_awesome_star {}", font_awesome_star.escape_debug());
|
||||||
assert_eq!(unicode_column_width(font_awesome_star), 1);
|
assert_eq!(unicode_column_width(font_awesome_star, None), 1);
|
||||||
|
|
||||||
|
let england_flag = "\u{1f3f4}\u{e0067}\u{e0062}\u{e0065}\u{e006e}\u{e0067}\u{e007f}";
|
||||||
|
assert_eq!(unicode_column_width(england_flag, None), 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn issue_1161() {
|
fn issue_1161() {
|
||||||
let x_ideographic_space_x = "x\u{3000}x";
|
let x_ideographic_space_x = "x\u{3000}x";
|
||||||
assert_eq!(unicode_column_width(x_ideographic_space_x), 4);
|
assert_eq!(unicode_column_width(x_ideographic_space_x, None), 4);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
x_ideographic_space_x.graphemes(true).collect::<Vec<_>>(),
|
x_ideographic_space_x.graphemes(true).collect::<Vec<_>>(),
|
||||||
vec!["x".to_string(), "\u{3000}".to_string(), "x".to_string()],
|
vec!["x".to_string(), "\u{3000}".to_string(), "x".to_string()],
|
||||||
@ -964,8 +1037,11 @@ mod test {
|
|||||||
let victory_hand = "\u{270c}";
|
let victory_hand = "\u{270c}";
|
||||||
let victory_hand_text_presentation = "\u{270c}\u{fe0e}";
|
let victory_hand_text_presentation = "\u{270c}\u{fe0e}";
|
||||||
|
|
||||||
assert_eq!(unicode_column_width(victory_hand_text_presentation), 1);
|
assert_eq!(
|
||||||
assert_eq!(unicode_column_width(victory_hand), 1);
|
unicode_column_width(victory_hand_text_presentation, None),
|
||||||
|
1
|
||||||
|
);
|
||||||
|
assert_eq!(unicode_column_width(victory_hand, None), 1);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
victory_hand_text_presentation
|
victory_hand_text_presentation
|
||||||
@ -985,7 +1061,11 @@ mod test {
|
|||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
vec![copyright_emoji_presentation.to_string()]
|
vec![copyright_emoji_presentation.to_string()]
|
||||||
);
|
);
|
||||||
assert_eq!(unicode_column_width(copyright_emoji_presentation), 2);
|
assert_eq!(unicode_column_width(copyright_emoji_presentation, None), 2);
|
||||||
|
assert_eq!(
|
||||||
|
unicode_column_width(copyright_emoji_presentation, Some(UnicodeVersion(9))),
|
||||||
|
1
|
||||||
|
);
|
||||||
|
|
||||||
let copyright_text_presentation = "\u{00A9}";
|
let copyright_text_presentation = "\u{00A9}";
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@ -994,7 +1074,7 @@ mod test {
|
|||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
vec![copyright_text_presentation.to_string()]
|
vec![copyright_text_presentation.to_string()]
|
||||||
);
|
);
|
||||||
assert_eq!(unicode_column_width(copyright_text_presentation), 1);
|
assert_eq!(unicode_column_width(copyright_text_presentation, None), 1);
|
||||||
|
|
||||||
let raised_fist = "\u{270a}";
|
let raised_fist = "\u{270a}";
|
||||||
let raised_fist_text = "\u{270a}\u{fe0e}";
|
let raised_fist_text = "\u{270a}\u{fe0e}";
|
||||||
@ -1002,12 +1082,12 @@ mod test {
|
|||||||
Presentation::for_grapheme(raised_fist),
|
Presentation::for_grapheme(raised_fist),
|
||||||
(Presentation::Emoji, None)
|
(Presentation::Emoji, None)
|
||||||
);
|
);
|
||||||
assert_eq!(unicode_column_width(raised_fist), 2);
|
assert_eq!(unicode_column_width(raised_fist, None), 2);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Presentation::for_grapheme(raised_fist_text),
|
Presentation::for_grapheme(raised_fist_text),
|
||||||
(Presentation::Emoji, Some(Presentation::Text))
|
(Presentation::Emoji, Some(Presentation::Text))
|
||||||
);
|
);
|
||||||
assert_eq!(unicode_column_width(raised_fist_text), 1);
|
assert_eq!(unicode_column_width(raised_fist_text, None), 1);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
raised_fist_text.graphemes(true).collect::<Vec<_>>(),
|
raised_fist_text.graphemes(true).collect::<Vec<_>>(),
|
||||||
|
@ -11,8 +11,10 @@ use std::borrow::Cow;
|
|||||||
pub struct CellCluster {
|
pub struct CellCluster {
|
||||||
pub attrs: CellAttributes,
|
pub attrs: CellAttributes,
|
||||||
pub text: String,
|
pub text: String,
|
||||||
|
pub width: usize,
|
||||||
pub presentation: Presentation,
|
pub presentation: Presentation,
|
||||||
byte_to_cell_idx: Vec<usize>,
|
byte_to_cell_idx: Vec<usize>,
|
||||||
|
byte_to_cell_width: Vec<u8>,
|
||||||
pub first_cell_idx: usize,
|
pub first_cell_idx: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,6 +29,14 @@ impl CellCluster {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn byte_to_cell_width(&self, byte_idx: usize) -> u8 {
|
||||||
|
if self.byte_to_cell_width.is_empty() {
|
||||||
|
1
|
||||||
|
} else {
|
||||||
|
self.byte_to_cell_width[byte_idx]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Compute the list of CellClusters from a set of visible cells.
|
/// Compute the list of CellClusters from a set of visible cells.
|
||||||
/// The input is typically the result of calling `Line::visible_cells()`.
|
/// The input is typically the result of calling `Line::visible_cells()`.
|
||||||
pub fn make_cluster<'a>(
|
pub fn make_cluster<'a>(
|
||||||
@ -60,6 +70,7 @@ impl CellCluster {
|
|||||||
normalized_attr.into_owned(),
|
normalized_attr.into_owned(),
|
||||||
cell_str,
|
cell_str,
|
||||||
cell_idx,
|
cell_idx,
|
||||||
|
c.width(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
Some(mut last) => {
|
Some(mut last) => {
|
||||||
@ -75,6 +86,7 @@ impl CellCluster {
|
|||||||
normalized_attr.into_owned(),
|
normalized_attr.into_owned(),
|
||||||
cell_str,
|
cell_str,
|
||||||
cell_idx,
|
cell_idx,
|
||||||
|
c.width(),
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
// Add to current cluster.
|
// Add to current cluster.
|
||||||
@ -102,9 +114,10 @@ impl CellCluster {
|
|||||||
normalized_attr.into_owned(),
|
normalized_attr.into_owned(),
|
||||||
cell_str,
|
cell_str,
|
||||||
cell_idx,
|
cell_idx,
|
||||||
|
c.width(),
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
last.add(cell_str, cell_idx);
|
last.add(cell_str, cell_idx, c.width());
|
||||||
Some(last)
|
Some(last)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -127,6 +140,7 @@ impl CellCluster {
|
|||||||
attrs: CellAttributes,
|
attrs: CellAttributes,
|
||||||
text: &str,
|
text: &str,
|
||||||
cell_idx: usize,
|
cell_idx: usize,
|
||||||
|
width: usize,
|
||||||
) -> CellCluster {
|
) -> CellCluster {
|
||||||
let mut idx = Vec::new();
|
let mut idx = Vec::new();
|
||||||
if text.len() > 1 {
|
if text.len() > 1 {
|
||||||
@ -137,20 +151,30 @@ impl CellCluster {
|
|||||||
idx.push(cell_idx);
|
idx.push(cell_idx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut byte_to_cell_width = Vec::new();
|
||||||
|
if width > 1 {
|
||||||
|
for _ in 0..text.len() {
|
||||||
|
byte_to_cell_width.push(width as u8);
|
||||||
|
}
|
||||||
|
}
|
||||||
let mut storage = String::with_capacity(hint);
|
let mut storage = String::with_capacity(hint);
|
||||||
storage.push_str(text);
|
storage.push_str(text);
|
||||||
|
|
||||||
CellCluster {
|
CellCluster {
|
||||||
attrs,
|
attrs,
|
||||||
|
width,
|
||||||
text: storage,
|
text: storage,
|
||||||
presentation,
|
presentation,
|
||||||
byte_to_cell_idx: idx,
|
byte_to_cell_idx: idx,
|
||||||
|
byte_to_cell_width,
|
||||||
first_cell_idx: cell_idx,
|
first_cell_idx: cell_idx,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add to this cluster
|
/// Add to this cluster
|
||||||
fn add(&mut self, text: &str, cell_idx: usize) {
|
fn add(&mut self, text: &str, cell_idx: usize, width: usize) {
|
||||||
|
self.width += width;
|
||||||
if !self.byte_to_cell_idx.is_empty() {
|
if !self.byte_to_cell_idx.is_empty() {
|
||||||
// We had at least one multi-byte cell in the past
|
// We had at least one multi-byte cell in the past
|
||||||
for _ in 0..text.len() {
|
for _ in 0..text.len() {
|
||||||
@ -166,6 +190,22 @@ impl CellCluster {
|
|||||||
self.byte_to_cell_idx.push(cell_idx);
|
self.byte_to_cell_idx.push(cell_idx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !self.byte_to_cell_width.is_empty() {
|
||||||
|
// We had at least one double-wide cell in the past
|
||||||
|
for _ in 0..text.len() {
|
||||||
|
self.byte_to_cell_width.push(width as u8);
|
||||||
|
}
|
||||||
|
} else if width > 1 {
|
||||||
|
// Extrapolate the widths so far; they must all be single width
|
||||||
|
for _ in 0..self.text.len() {
|
||||||
|
self.byte_to_cell_width.push(1);
|
||||||
|
}
|
||||||
|
// and add the current double width cell
|
||||||
|
for _ in 0..text.len() {
|
||||||
|
self.byte_to_cell_width.push(width as u8);
|
||||||
|
}
|
||||||
|
}
|
||||||
self.text.push_str(text);
|
self.text.push_str(text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -67,7 +67,7 @@ pub trait LineEditorHost {
|
|||||||
/// as well as textual output.
|
/// as well as textual output.
|
||||||
/// The default implementation returns the line as-is with no coloring.
|
/// The default implementation returns the line as-is with no coloring.
|
||||||
fn highlight_line(&self, line: &str, cursor_position: usize) -> (Vec<OutputElement>, usize) {
|
fn highlight_line(&self, line: &str, cursor_position: usize) -> (Vec<OutputElement>, usize) {
|
||||||
let cursor_x_pos = crate::cell::unicode_column_width(&line[0..cursor_position]);
|
let cursor_x_pos = crate::cell::unicode_column_width(&line[0..cursor_position], None);
|
||||||
(vec![OutputElement::Text(line.to_owned())], cursor_x_pos)
|
(vec![OutputElement::Text(line.to_owned())], cursor_x_pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,7 +203,7 @@ impl ChangeSequence {
|
|||||||
self.cursor_y += 1;
|
self.cursor_y += 1;
|
||||||
self.cursor_x = 0;
|
self.cursor_x = 0;
|
||||||
} else {
|
} else {
|
||||||
let len = unicode_column_width(g);
|
let len = unicode_column_width(g, None);
|
||||||
self.cursor_x += len;
|
self.cursor_x += len;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,3 +7,8 @@ echo -e "\u270c\ufe0f Victory hand, explicit emoji presentation"
|
|||||||
echo -e "\u270a Raised fist, emoji presentation by default"
|
echo -e "\u270a Raised fist, emoji presentation by default"
|
||||||
echo -e "\u270a\ufe0e Raised fist, explicit text presentation"
|
echo -e "\u270a\ufe0e Raised fist, explicit text presentation"
|
||||||
echo -e "\u270a\ufe0f Raised fist, explicit emoji presentation"
|
echo -e "\u270a\ufe0f Raised fist, explicit emoji presentation"
|
||||||
|
|
||||||
|
echo -e "\u2716 Multiply, text presentation by default"
|
||||||
|
echo -e "\u2716\ufe0e Multiply, explicit text presentation"
|
||||||
|
echo -e "\u2716\ufe0f Multiply, explicit emoji presentation"
|
||||||
|
|
||||||
|
@ -659,7 +659,7 @@ impl RenderableState {
|
|||||||
let col = inner
|
let col = inner
|
||||||
.dimensions
|
.dimensions
|
||||||
.cols
|
.cols
|
||||||
.saturating_sub(wezterm_term::unicode_column_width(&status));
|
.saturating_sub(wezterm_term::unicode_column_width(&status, None));
|
||||||
|
|
||||||
let mut attr = CellAttributes::default();
|
let mut attr = CellAttributes::default();
|
||||||
attr.set_foreground(AnsiColor::White);
|
attr.set_foreground(AnsiColor::White);
|
||||||
|
@ -9,7 +9,7 @@ use log::error;
|
|||||||
use ordered_float::NotNan;
|
use ordered_float::NotNan;
|
||||||
use std::cell::{RefCell, RefMut};
|
use std::cell::{RefCell, RefMut};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use termwiz::cell::{unicode_column_width, Presentation};
|
use termwiz::cell::Presentation;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
@ -25,13 +25,11 @@ struct Info {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn make_glyphinfo(text: &str, font_idx: usize, info: &Info) -> GlyphInfo {
|
fn make_glyphinfo(text: &str, font_idx: usize, info: &Info) -> GlyphInfo {
|
||||||
let num_cells = unicode_column_width(text) as u8;
|
|
||||||
let is_space = text == " ";
|
let is_space = text == " ";
|
||||||
GlyphInfo {
|
GlyphInfo {
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
text: text.into(),
|
text: text.into(),
|
||||||
is_space,
|
is_space,
|
||||||
num_cells,
|
|
||||||
font_idx,
|
font_idx,
|
||||||
glyph_pos: info.codepoint,
|
glyph_pos: info.codepoint,
|
||||||
cluster: info.cluster as u32,
|
cluster: info.cluster as u32,
|
||||||
@ -585,7 +583,6 @@ mod test {
|
|||||||
is_space: false,
|
is_space: false,
|
||||||
font_idx: 0,
|
font_idx: 0,
|
||||||
glyph_pos: 180,
|
glyph_pos: 180,
|
||||||
num_cells: 1,
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
text: "a".into(),
|
text: "a".into(),
|
||||||
x_advance: PixelLength::new(6.),
|
x_advance: PixelLength::new(6.),
|
||||||
@ -598,7 +595,6 @@ mod test {
|
|||||||
is_space: false,
|
is_space: false,
|
||||||
font_idx: 0,
|
font_idx: 0,
|
||||||
glyph_pos: 205,
|
glyph_pos: 205,
|
||||||
num_cells: 1,
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
text: "b".into(),
|
text: "b".into(),
|
||||||
x_advance: PixelLength::new(6.),
|
x_advance: PixelLength::new(6.),
|
||||||
@ -611,7 +607,6 @@ mod test {
|
|||||||
is_space: false,
|
is_space: false,
|
||||||
font_idx: 0,
|
font_idx: 0,
|
||||||
glyph_pos: 206,
|
glyph_pos: 206,
|
||||||
num_cells: 1,
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
text: "c".into(),
|
text: "c".into(),
|
||||||
x_advance: PixelLength::new(6.),
|
x_advance: PixelLength::new(6.),
|
||||||
@ -633,7 +628,6 @@ mod test {
|
|||||||
is_space: false,
|
is_space: false,
|
||||||
font_idx: 0,
|
font_idx: 0,
|
||||||
glyph_pos: 726,
|
glyph_pos: 726,
|
||||||
num_cells: 1,
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
text: "<".into(),
|
text: "<".into(),
|
||||||
x_advance: PixelLength::new(6.),
|
x_advance: PixelLength::new(6.),
|
||||||
@ -657,7 +651,6 @@ mod test {
|
|||||||
is_space: false,
|
is_space: false,
|
||||||
font_idx: 0,
|
font_idx: 0,
|
||||||
glyph_pos: 1212,
|
glyph_pos: 1212,
|
||||||
num_cells: 1,
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
text: "<".into(),
|
text: "<".into(),
|
||||||
x_advance: PixelLength::new(6.),
|
x_advance: PixelLength::new(6.),
|
||||||
@ -670,7 +663,6 @@ mod test {
|
|||||||
is_space: false,
|
is_space: false,
|
||||||
font_idx: 0,
|
font_idx: 0,
|
||||||
glyph_pos: 1065,
|
glyph_pos: 1065,
|
||||||
num_cells: 1,
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
text: "-".into(),
|
text: "-".into(),
|
||||||
x_advance: PixelLength::new(6.),
|
x_advance: PixelLength::new(6.),
|
||||||
@ -693,7 +685,6 @@ mod test {
|
|||||||
is_space: false,
|
is_space: false,
|
||||||
font_idx: 0,
|
font_idx: 0,
|
||||||
glyph_pos: 726,
|
glyph_pos: 726,
|
||||||
num_cells: 1,
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
text: "<".into(),
|
text: "<".into(),
|
||||||
x_advance: PixelLength::new(6.),
|
x_advance: PixelLength::new(6.),
|
||||||
@ -706,7 +697,6 @@ mod test {
|
|||||||
is_space: false,
|
is_space: false,
|
||||||
font_idx: 0,
|
font_idx: 0,
|
||||||
glyph_pos: 1212,
|
glyph_pos: 1212,
|
||||||
num_cells: 1,
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
text: "-".into(),
|
text: "-".into(),
|
||||||
x_advance: PixelLength::new(6.),
|
x_advance: PixelLength::new(6.),
|
||||||
@ -719,7 +709,6 @@ mod test {
|
|||||||
is_space: false,
|
is_space: false,
|
||||||
font_idx: 0,
|
font_idx: 0,
|
||||||
glyph_pos: 623,
|
glyph_pos: 623,
|
||||||
num_cells: 1,
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
text: "-".into(),
|
text: "-".into(),
|
||||||
x_advance: PixelLength::new(6.),
|
x_advance: PixelLength::new(6.),
|
||||||
@ -743,7 +732,6 @@ mod test {
|
|||||||
is_space: false,
|
is_space: false,
|
||||||
font_idx: 0,
|
font_idx: 0,
|
||||||
glyph_pos: 350,
|
glyph_pos: 350,
|
||||||
num_cells: 1,
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
text: "x".into(),
|
text: "x".into(),
|
||||||
x_advance: PixelLength::new(6.),
|
x_advance: PixelLength::new(6.),
|
||||||
@ -756,7 +744,6 @@ mod test {
|
|||||||
text: " ".into(),
|
text: " ".into(),
|
||||||
is_space: true,
|
is_space: true,
|
||||||
cluster: 1,
|
cluster: 1,
|
||||||
num_cells: 1,
|
|
||||||
font_idx: 0,
|
font_idx: 0,
|
||||||
glyph_pos: 686,
|
glyph_pos: 686,
|
||||||
x_advance: PixelLength::new(6.),
|
x_advance: PixelLength::new(6.),
|
||||||
@ -769,7 +756,6 @@ mod test {
|
|||||||
is_space: false,
|
is_space: false,
|
||||||
font_idx: 0,
|
font_idx: 0,
|
||||||
glyph_pos: 350,
|
glyph_pos: 350,
|
||||||
num_cells: 1,
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
text: "x".into(),
|
text: "x".into(),
|
||||||
x_advance: PixelLength::new(6.),
|
x_advance: PixelLength::new(6.),
|
||||||
@ -795,7 +781,6 @@ mod test {
|
|||||||
is_space: false,
|
is_space: false,
|
||||||
font_idx: 0,
|
font_idx: 0,
|
||||||
glyph_pos: 350,
|
glyph_pos: 350,
|
||||||
num_cells: 1,
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
text: "x".into(),
|
text: "x".into(),
|
||||||
x_advance: PixelLength::new(6.),
|
x_advance: PixelLength::new(6.),
|
||||||
@ -808,7 +793,6 @@ mod test {
|
|||||||
text: "\u{3000}".into(),
|
text: "\u{3000}".into(),
|
||||||
is_space: false,
|
is_space: false,
|
||||||
cluster: 1,
|
cluster: 1,
|
||||||
num_cells: 2,
|
|
||||||
font_idx: 0,
|
font_idx: 0,
|
||||||
glyph_pos: 686,
|
glyph_pos: 686,
|
||||||
x_advance: PixelLength::new(10.),
|
x_advance: PixelLength::new(10.),
|
||||||
@ -821,7 +805,6 @@ mod test {
|
|||||||
is_space: false,
|
is_space: false,
|
||||||
font_idx: 0,
|
font_idx: 0,
|
||||||
glyph_pos: 350,
|
glyph_pos: 350,
|
||||||
num_cells: 1,
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
text: "x".into(),
|
text: "x".into(),
|
||||||
x_advance: PixelLength::new(6.),
|
x_advance: PixelLength::new(6.),
|
||||||
|
@ -13,8 +13,6 @@ pub struct GlyphInfo {
|
|||||||
pub is_space: bool,
|
pub is_space: bool,
|
||||||
/// Offset within text
|
/// Offset within text
|
||||||
pub cluster: u32,
|
pub cluster: u32,
|
||||||
/// How many cells/columns this glyph occupies horizontally
|
|
||||||
pub num_cells: u8,
|
|
||||||
/// Which font alternative to use; index into Font.fonts
|
/// Which font alternative to use; index into Font.fonts
|
||||||
pub font_idx: FallbackIdx,
|
pub font_idx: FallbackIdx,
|
||||||
/// Which freetype glyph to load
|
/// Which freetype glyph to load
|
||||||
|
@ -56,6 +56,7 @@ pub struct SizedBlockKey {
|
|||||||
pub struct GlyphKey {
|
pub struct GlyphKey {
|
||||||
pub font_idx: usize,
|
pub font_idx: usize,
|
||||||
pub glyph_pos: u32,
|
pub glyph_pos: u32,
|
||||||
|
pub num_cells: u8,
|
||||||
pub style: TextStyle,
|
pub style: TextStyle,
|
||||||
pub followed_by_space: bool,
|
pub followed_by_space: bool,
|
||||||
pub metric: CellMetricKey,
|
pub metric: CellMetricKey,
|
||||||
@ -72,6 +73,7 @@ pub struct GlyphKey {
|
|||||||
pub struct BorrowedGlyphKey<'a> {
|
pub struct BorrowedGlyphKey<'a> {
|
||||||
pub font_idx: usize,
|
pub font_idx: usize,
|
||||||
pub glyph_pos: u32,
|
pub glyph_pos: u32,
|
||||||
|
pub num_cells: u8,
|
||||||
pub style: &'a TextStyle,
|
pub style: &'a TextStyle,
|
||||||
pub followed_by_space: bool,
|
pub followed_by_space: bool,
|
||||||
pub metric: CellMetricKey,
|
pub metric: CellMetricKey,
|
||||||
@ -84,6 +86,7 @@ impl<'a> BorrowedGlyphKey<'a> {
|
|||||||
GlyphKey {
|
GlyphKey {
|
||||||
font_idx: self.font_idx,
|
font_idx: self.font_idx,
|
||||||
glyph_pos: self.glyph_pos,
|
glyph_pos: self.glyph_pos,
|
||||||
|
num_cells: self.num_cells,
|
||||||
style: self.style.clone(),
|
style: self.style.clone(),
|
||||||
followed_by_space: self.followed_by_space,
|
followed_by_space: self.followed_by_space,
|
||||||
metric: self.metric,
|
metric: self.metric,
|
||||||
@ -101,6 +104,7 @@ impl GlyphKeyTrait for GlyphKey {
|
|||||||
BorrowedGlyphKey {
|
BorrowedGlyphKey {
|
||||||
font_idx: self.font_idx,
|
font_idx: self.font_idx,
|
||||||
glyph_pos: self.glyph_pos,
|
glyph_pos: self.glyph_pos,
|
||||||
|
num_cells: self.num_cells,
|
||||||
style: &self.style,
|
style: &self.style,
|
||||||
followed_by_space: self.followed_by_space,
|
followed_by_space: self.followed_by_space,
|
||||||
metric: self.metric,
|
metric: self.metric,
|
||||||
@ -345,10 +349,12 @@ impl<T: Texture2d> GlyphCache<T> {
|
|||||||
followed_by_space: bool,
|
followed_by_space: bool,
|
||||||
font: &Rc<LoadedFont>,
|
font: &Rc<LoadedFont>,
|
||||||
metrics: &RenderMetrics,
|
metrics: &RenderMetrics,
|
||||||
|
num_cells: u8,
|
||||||
) -> anyhow::Result<Rc<CachedGlyph<T>>> {
|
) -> anyhow::Result<Rc<CachedGlyph<T>>> {
|
||||||
let key = BorrowedGlyphKey {
|
let key = BorrowedGlyphKey {
|
||||||
font_idx: info.font_idx,
|
font_idx: info.font_idx,
|
||||||
glyph_pos: info.glyph_pos,
|
glyph_pos: info.glyph_pos,
|
||||||
|
num_cells: num_cells,
|
||||||
style,
|
style,
|
||||||
followed_by_space,
|
followed_by_space,
|
||||||
metric: metrics.into(),
|
metric: metrics.into(),
|
||||||
@ -361,7 +367,7 @@ impl<T: Texture2d> GlyphCache<T> {
|
|||||||
}
|
}
|
||||||
metrics::histogram!("glyph_cache.glyph_cache.miss.rate", 1.);
|
metrics::histogram!("glyph_cache.glyph_cache.miss.rate", 1.);
|
||||||
|
|
||||||
let glyph = match self.load_glyph(info, font, followed_by_space) {
|
let glyph = match self.load_glyph(info, font, followed_by_space, num_cells) {
|
||||||
Ok(g) => g,
|
Ok(g) => g,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
if err
|
if err
|
||||||
@ -407,6 +413,7 @@ impl<T: Texture2d> GlyphCache<T> {
|
|||||||
info: &GlyphInfo,
|
info: &GlyphInfo,
|
||||||
font: &Rc<LoadedFont>,
|
font: &Rc<LoadedFont>,
|
||||||
followed_by_space: bool,
|
followed_by_space: bool,
|
||||||
|
num_cells: u8,
|
||||||
) -> anyhow::Result<Rc<CachedGlyph<T>>> {
|
) -> anyhow::Result<Rc<CachedGlyph<T>>> {
|
||||||
let base_metrics;
|
let base_metrics;
|
||||||
let idx_metrics;
|
let idx_metrics;
|
||||||
@ -442,7 +449,7 @@ impl<T: Texture2d> GlyphCache<T> {
|
|||||||
// can happen somehow; see <https://github.com/wez/wezterm/issues/1042>
|
// can happen somehow; see <https://github.com/wez/wezterm/issues/1042>
|
||||||
// so let's treat 0 cells as 1 cell so that we don't try to divide by
|
// so let's treat 0 cells as 1 cell so that we don't try to divide by
|
||||||
// zero below.
|
// zero below.
|
||||||
let num_cells = info.num_cells.max(1) as f64;
|
let num_cells = num_cells.max(1) as f64;
|
||||||
|
|
||||||
// Maximum width allowed for this glyph based on its unicode width and
|
// Maximum width allowed for this glyph based on its unicode width and
|
||||||
// the dimensions of a cell
|
// the dimensions of a cell
|
||||||
|
@ -54,7 +54,7 @@ impl RenderState {
|
|||||||
|
|
||||||
fn wrap_text(&mut self, text: &str) {
|
fn wrap_text(&mut self, text: &str) {
|
||||||
for word in text.split_word_bounds() {
|
for word in text.split_word_bounds() {
|
||||||
let len = unicode_column_width(word);
|
let len = unicode_column_width(word, None);
|
||||||
if self.x_pos + len < self.wrap_width {
|
if self.x_pos + len < self.wrap_width {
|
||||||
if !(self.x_pos == 0 && is_whitespace_word(word)) {
|
if !(self.x_pos == 0 && is_whitespace_word(word)) {
|
||||||
self.changes.push(word.into());
|
self.changes.push(word.into());
|
||||||
@ -110,7 +110,7 @@ impl RenderState {
|
|||||||
} else {
|
} else {
|
||||||
" * ".to_owned()
|
" * ".to_owned()
|
||||||
};
|
};
|
||||||
let indent_width = unicode_column_width(&list_item_prefix);
|
let indent_width = unicode_column_width(&list_item_prefix, None);
|
||||||
self.current_indent.replace(indent_width);
|
self.current_indent.replace(indent_width);
|
||||||
self.changes.push(list_item_prefix.into());
|
self.changes.push(list_item_prefix.into());
|
||||||
self.x_pos += indent_width;
|
self.x_pos += indent_width;
|
||||||
|
@ -296,7 +296,7 @@ impl CopyRenderable {
|
|||||||
let mut last_was_whitespace = false;
|
let mut last_was_whitespace = false;
|
||||||
|
|
||||||
for (idx, word) in s.split_word_bounds().rev().enumerate() {
|
for (idx, word) in s.split_word_bounds().rev().enumerate() {
|
||||||
let width = unicode_column_width(word);
|
let width = unicode_column_width(word, None);
|
||||||
|
|
||||||
if is_whitespace_word(word) {
|
if is_whitespace_word(word) {
|
||||||
self.cursor.x = self.cursor.x.saturating_sub(width);
|
self.cursor.x = self.cursor.x.saturating_sub(width);
|
||||||
@ -335,13 +335,13 @@ impl CopyRenderable {
|
|||||||
let mut words = s.split_word_bounds();
|
let mut words = s.split_word_bounds();
|
||||||
|
|
||||||
if let Some(word) = words.next() {
|
if let Some(word) = words.next() {
|
||||||
self.cursor.x += unicode_column_width(word);
|
self.cursor.x += unicode_column_width(word, None);
|
||||||
if !is_whitespace_word(word) {
|
if !is_whitespace_word(word) {
|
||||||
// We were part-way through a word, so look
|
// We were part-way through a word, so look
|
||||||
// at the next word
|
// at the next word
|
||||||
if let Some(word) = words.next() {
|
if let Some(word) = words.next() {
|
||||||
if is_whitespace_word(word) {
|
if is_whitespace_word(word) {
|
||||||
self.cursor.x += unicode_column_width(word);
|
self.cursor.x += unicode_column_width(word, None);
|
||||||
// If we advance off the RHS, move to the start of the word on the
|
// If we advance off the RHS, move to the start of the word on the
|
||||||
// next line, if any!
|
// next line, if any!
|
||||||
if self.cursor.x >= width {
|
if self.cursor.x >= width {
|
||||||
|
@ -400,7 +400,7 @@ impl Pane for QuickSelectOverlay {
|
|||||||
// move to the search box
|
// move to the search box
|
||||||
let renderer = self.renderer.borrow();
|
let renderer = self.renderer.borrow();
|
||||||
StableCursorPosition {
|
StableCursorPosition {
|
||||||
x: 8 + wezterm_term::unicode_column_width(&renderer.selection),
|
x: 8 + wezterm_term::unicode_column_width(&renderer.selection, None),
|
||||||
y: renderer.compute_search_row(),
|
y: renderer.compute_search_row(),
|
||||||
shape: termwiz::surface::CursorShape::SteadyBlock,
|
shape: termwiz::surface::CursorShape::SteadyBlock,
|
||||||
visibility: termwiz::surface::CursorVisibility::Visible,
|
visibility: termwiz::surface::CursorVisibility::Visible,
|
||||||
|
@ -271,7 +271,7 @@ impl Pane for SearchOverlay {
|
|||||||
// move to the search box
|
// move to the search box
|
||||||
let renderer = self.renderer.borrow();
|
let renderer = self.renderer.borrow();
|
||||||
StableCursorPosition {
|
StableCursorPosition {
|
||||||
x: 8 + wezterm_term::unicode_column_width(&renderer.pattern),
|
x: 8 + wezterm_term::unicode_column_width(&renderer.pattern, None),
|
||||||
y: renderer.compute_search_row(),
|
y: renderer.compute_search_row(),
|
||||||
shape: termwiz::surface::CursorShape::SteadyBlock,
|
shape: termwiz::surface::CursorShape::SteadyBlock,
|
||||||
visibility: termwiz::surface::CursorVisibility::Visible,
|
visibility: termwiz::surface::CursorVisibility::Visible,
|
||||||
|
@ -45,7 +45,7 @@ where
|
|||||||
/// This function's goal is to handle those two cases.
|
/// This function's goal is to handle those two cases.
|
||||||
pub fn process(
|
pub fn process(
|
||||||
render_metrics: &RenderMetrics,
|
render_metrics: &RenderMetrics,
|
||||||
_cluster: &CellCluster,
|
cluster: &CellCluster,
|
||||||
infos: &[GlyphInfo],
|
infos: &[GlyphInfo],
|
||||||
glyphs: &[Rc<CachedGlyph<T>>],
|
glyphs: &[Rc<CachedGlyph<T>>],
|
||||||
) -> Vec<ShapedInfo<T>> {
|
) -> Vec<ShapedInfo<T>> {
|
||||||
@ -57,6 +57,7 @@ where
|
|||||||
let simple_mode = !config::configuration().experimental_shape_post_processing;
|
let simple_mode = !config::configuration().experimental_shape_post_processing;
|
||||||
|
|
||||||
for (info, glyph) in infos.iter().zip(glyphs.iter()) {
|
for (info, glyph) in infos.iter().zip(glyphs.iter()) {
|
||||||
|
let info_num_cells = cluster.byte_to_cell_width(info.cluster as usize);
|
||||||
if simple_mode {
|
if simple_mode {
|
||||||
pos.push(Some(ShapedInfo {
|
pos.push(Some(ShapedInfo {
|
||||||
pos: GlyphPosition {
|
pos: GlyphPosition {
|
||||||
@ -65,7 +66,7 @@ where
|
|||||||
.texture
|
.texture
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map_or(0, |t| t.coords.width() as u32),
|
.map_or(0, |t| t.coords.width() as u32),
|
||||||
num_cells: info.num_cells,
|
num_cells: info_num_cells,
|
||||||
x_offset: info.x_offset,
|
x_offset: info.x_offset,
|
||||||
bearing_x: glyph.bearing_x.get() as f32,
|
bearing_x: glyph.bearing_x.get() as f32,
|
||||||
},
|
},
|
||||||
@ -131,16 +132,16 @@ where
|
|||||||
.texture
|
.texture
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map_or(0, |t| t.coords.width() as u32);
|
.map_or(0, |t| t.coords.width() as u32);
|
||||||
let num_cells = if info.num_cells == 1
|
let num_cells = if info_num_cells == 1
|
||||||
// Only adjust the cell count if this glyph is wide enough
|
// Only adjust the cell count if this glyph is wide enough
|
||||||
&& glyph_width > (1.5 * render_metrics.cell_size.width as f64)
|
&& glyph_width > (1.5 * render_metrics.cell_size.width as f64)
|
||||||
{
|
{
|
||||||
(glyph_width / render_metrics.cell_size.width as f64).ceil() as u8
|
(glyph_width / render_metrics.cell_size.width as f64).ceil() as u8
|
||||||
} else {
|
} else {
|
||||||
info.num_cells
|
info_num_cells
|
||||||
};
|
};
|
||||||
let bearing_x = if num_cells > info.num_cells && glyph.bearing_x.get() < 0. {
|
let bearing_x = if num_cells > info_num_cells && glyph.bearing_x.get() < 0. {
|
||||||
((num_cells - info.num_cells) as f64 * render_metrics.cell_size.width as f64)
|
((num_cells - info_num_cells) as f64 * render_metrics.cell_size.width as f64)
|
||||||
+ glyph.bearing_x.get()
|
+ glyph.bearing_x.get()
|
||||||
} else {
|
} else {
|
||||||
glyph.bearing_x.get()
|
glyph.bearing_x.get()
|
||||||
@ -163,7 +164,7 @@ where
|
|||||||
.texture
|
.texture
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map_or(0, |t| t.coords.width() as u32),
|
.map_or(0, |t| t.coords.width() as u32),
|
||||||
num_cells: info.num_cells,
|
num_cells: info_num_cells,
|
||||||
x_offset: info.x_offset,
|
x_offset: info.x_offset,
|
||||||
bearing_x: glyph.bearing_x.get() as f32,
|
bearing_x: glyph.bearing_x.get() as f32,
|
||||||
},
|
},
|
||||||
@ -281,6 +282,7 @@ mod test {
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|info| {
|
.map(|info| {
|
||||||
let cell_idx = cluster.byte_to_cell_idx(info.cluster as usize);
|
let cell_idx = cluster.byte_to_cell_idx(info.cluster as usize);
|
||||||
|
let num_cells = cluster.byte_to_cell_width(info.cluster as usize);
|
||||||
|
|
||||||
let followed_by_space = match line.cells().get(cell_idx + 1) {
|
let followed_by_space = match line.cells().get(cell_idx + 1) {
|
||||||
Some(cell) => cell.str() == " ",
|
Some(cell) => cell.str() == " ",
|
||||||
@ -288,7 +290,14 @@ mod test {
|
|||||||
};
|
};
|
||||||
|
|
||||||
glyph_cache
|
glyph_cache
|
||||||
.cached_glyph(info, &style, followed_by_space, font, render_metrics)
|
.cached_glyph(
|
||||||
|
info,
|
||||||
|
&style,
|
||||||
|
followed_by_space,
|
||||||
|
font,
|
||||||
|
render_metrics,
|
||||||
|
num_cells,
|
||||||
|
)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
@ -81,7 +81,7 @@ fn call_format_tab_title(
|
|||||||
_ => {
|
_ => {
|
||||||
let s = String::from_lua(v, &*lua)?;
|
let s = String::from_lua(v, &*lua)?;
|
||||||
Ok(Some(TitleText {
|
Ok(Some(TitleText {
|
||||||
len: unicode_column_width(&s),
|
len: unicode_column_width(&s, None),
|
||||||
items: vec![FormatItem::Text(s)],
|
items: vec![FormatItem::Text(s)],
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
@ -133,7 +133,7 @@ fn compute_tab_title(
|
|||||||
// this if there are too many tabs to fit the window at
|
// this if there are too many tabs to fit the window at
|
||||||
// this width.
|
// this width.
|
||||||
if !config.use_fancy_tab_bar {
|
if !config.use_fancy_tab_bar {
|
||||||
while unicode_column_width(&title) < 5 {
|
while unicode_column_width(&title, None) < 5 {
|
||||||
title.push(' ');
|
title.push(' ');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -143,7 +143,7 @@ fn compute_tab_title(
|
|||||||
};
|
};
|
||||||
|
|
||||||
TitleText {
|
TitleText {
|
||||||
len: unicode_column_width(&title),
|
len: unicode_column_width(&title, None),
|
||||||
items: vec![FormatItem::Text(title)],
|
items: vec![FormatItem::Text(title)],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ use smol::Timer;
|
|||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
|
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
|
||||||
use termwiz::cell::{unicode_column_width, Blink};
|
use termwiz::cell::Blink;
|
||||||
use termwiz::cellcluster::CellCluster;
|
use termwiz::cellcluster::CellCluster;
|
||||||
use termwiz::surface::{CursorShape, CursorVisibility};
|
use termwiz::surface::{CursorShape, CursorVisibility};
|
||||||
use wezterm_font::units::{IntPixelLength, PixelLength};
|
use wezterm_font::units::{IntPixelLength, PixelLength};
|
||||||
@ -1762,7 +1762,7 @@ impl super::TermWindow {
|
|||||||
for item in shaped {
|
for item in shaped {
|
||||||
let cluster = &item.cluster;
|
let cluster = &item.cluster;
|
||||||
let attrs = &cluster.attrs;
|
let attrs = &cluster.attrs;
|
||||||
let cluster_width = unicode_column_width(&cluster.text);
|
let cluster_width = cluster.width;
|
||||||
|
|
||||||
let bg_is_default = attrs.background() == ColorAttribute::Default;
|
let bg_is_default = attrs.background() == ColorAttribute::Default;
|
||||||
let bg_color = params.palette.resolve_bg(attrs.background());
|
let bg_color = params.palette.resolve_bg(attrs.background());
|
||||||
@ -2586,6 +2586,7 @@ impl super::TermWindow {
|
|||||||
let mut glyphs = Vec::with_capacity(infos.len());
|
let mut glyphs = Vec::with_capacity(infos.len());
|
||||||
for info in infos {
|
for info in infos {
|
||||||
let cell_idx = cluster.byte_to_cell_idx(info.cluster as usize);
|
let cell_idx = cluster.byte_to_cell_idx(info.cluster as usize);
|
||||||
|
let num_cells = cluster.byte_to_cell_width(info.cluster as usize);
|
||||||
|
|
||||||
if self.config.custom_block_glyphs {
|
if self.config.custom_block_glyphs {
|
||||||
if let Some(cell) = line.cells().get(cell_idx) {
|
if let Some(cell) = line.cells().get(cell_idx) {
|
||||||
@ -2620,6 +2621,7 @@ impl super::TermWindow {
|
|||||||
followed_by_space,
|
followed_by_space,
|
||||||
font,
|
font,
|
||||||
metrics,
|
metrics,
|
||||||
|
num_cells,
|
||||||
)?);
|
)?);
|
||||||
}
|
}
|
||||||
Ok(glyphs)
|
Ok(glyphs)
|
||||||
|
@ -45,5 +45,5 @@ rstest = "0.11"
|
|||||||
shell-words = "1.0"
|
shell-words = "1.0"
|
||||||
smol-potat = "1.1.2"
|
smol-potat = "1.1.2"
|
||||||
structopt = "0.3"
|
structopt = "0.3"
|
||||||
termwiz = { version = "0.13", path = "../termwiz" }
|
termwiz = { version = "0.14", path = "../termwiz" }
|
||||||
whoami = "1.1"
|
whoami = "1.1"
|
||||||
|
@ -26,12 +26,15 @@ impl LineEditorHost for PasswordPromptHost {
|
|||||||
// Rewrite the input so that we can obscure the password
|
// Rewrite the input so that we can obscure the password
|
||||||
// characters when output to the terminal widget
|
// characters when output to the terminal widget
|
||||||
let placeholder = "🔑";
|
let placeholder = "🔑";
|
||||||
let grapheme_count = unicode_column_width(line);
|
let grapheme_count = unicode_column_width(line, None);
|
||||||
let mut output = vec![];
|
let mut output = vec![];
|
||||||
for _ in 0..grapheme_count {
|
for _ in 0..grapheme_count {
|
||||||
output.push(OutputElement::Text(placeholder.to_string()));
|
output.push(OutputElement::Text(placeholder.to_string()));
|
||||||
}
|
}
|
||||||
(output, unicode_column_width(placeholder) * cursor_position)
|
(
|
||||||
|
output,
|
||||||
|
unicode_column_width(placeholder, None) * cursor_position,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user