mirror of
https://github.com/wez/wezterm.git
synced 2024-11-22 04:56:12 +03:00
bidi: tag Line with bidi mode
This commit refines bidi property handling: * experimental_bidi has been split into two new configuration settings; `bidi_enabled` (which controls whether the terminal performs implicit bidi processing) and `bidi_direction` which specifies the base direction and whether auto detection is enabled. * The `Line` type can now store those bidi properties (they are actually split across 3 bits representing enabled, auto-detection and direction) * The terminal now has a concept of active bidi properties and default bidi properties * The default properties are pulled from the wezterm configuration * active bidi properties are potentially set via escape sequences, BDSM (which sets bidi_enabled) and SCP (which sets bidi_direction). We don't support the 2501 temporary dec private mode suggested by the BIDI recommendation doc at this time. * When creating new `Line`'s or clearing from the start of a `Line`, the effective bidi properties are computed (from the active props, falling back to default propr) and applied to the `Line`. * When rendering the line, we now look at its bidi properties instead of just the global config. The default bidi properties are `bidi_enabled: false` and `bidi_direction: LeftToRight` which corresponds to the typical bidi-unaware mode of most terminals. It is possible to live reload the config to change the effective defaults, but note that they apply, by design, to new lines being processed through the terminal. That means existing output is left unaffected by a config reload, but subsequently printed lines will respect it. Pressing CTRL-L or otherwise contriving to have the running application refresh its display should cause the refreshed display to update and apply the new bidi mode. refs: #784
This commit is contained in:
parent
66b227bbf9
commit
98f35bbf24
3
Cargo.lock
generated
3
Cargo.lock
generated
@ -695,6 +695,7 @@ dependencies = [
|
||||
"toml",
|
||||
"umask",
|
||||
"unicode-segmentation",
|
||||
"wezterm-bidi",
|
||||
"wezterm-input-types",
|
||||
"wezterm-term",
|
||||
"winapi 0.3.9",
|
||||
@ -4668,6 +4669,7 @@ dependencies = [
|
||||
"k9",
|
||||
"log",
|
||||
"pretty_env_logger",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4934,6 +4936,7 @@ dependencies = [
|
||||
"unicode-segmentation",
|
||||
"unicode-width",
|
||||
"url",
|
||||
"wezterm-bidi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -5,8 +5,12 @@ edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[features]
|
||||
use_serde = ["serde"]
|
||||
|
||||
[dependencies]
|
||||
log = "0.4"
|
||||
serde = {version="1.0", features = ["derive"], optional=true}
|
||||
|
||||
[dev-dependencies]
|
||||
k9 = "0.11.0"
|
||||
|
@ -14,15 +14,42 @@ use bidi_brackets::BracketType;
|
||||
pub use bidi_class::BidiClass;
|
||||
pub use direction::Direction;
|
||||
pub use level::Level;
|
||||
#[cfg(feature = "use_serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Placeholder codepoint index that corresponds to NO_LEVEL
|
||||
const DELETED: usize = usize::max_value();
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "use_serde", derive(Serialize, Deserialize))]
|
||||
pub enum ParagraphDirectionHint {
|
||||
LeftToRight,
|
||||
RightToLeft,
|
||||
/// Attempt to auto-detect but fall back to LTR
|
||||
AutoLeftToRight,
|
||||
/// Attempt to auto-detect but fall back to RTL
|
||||
AutoRightToLeft,
|
||||
}
|
||||
|
||||
impl Default for ParagraphDirectionHint {
|
||||
fn default() -> Self {
|
||||
Self::LeftToRight
|
||||
}
|
||||
}
|
||||
|
||||
impl ParagraphDirectionHint {
|
||||
/// Returns just the direction portion of the hint, independent
|
||||
/// of the auto-detection state.
|
||||
pub fn direction(self) -> Direction {
|
||||
match self {
|
||||
ParagraphDirectionHint::AutoLeftToRight | ParagraphDirectionHint::LeftToRight => {
|
||||
Direction::LeftToRight
|
||||
}
|
||||
ParagraphDirectionHint::AutoRightToLeft | ParagraphDirectionHint::RightToLeft => {
|
||||
Direction::RightToLeft
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
@ -425,7 +452,12 @@ impl BidiContext {
|
||||
self.base_level = match hint {
|
||||
ParagraphDirectionHint::LeftToRight => Level(0),
|
||||
ParagraphDirectionHint::RightToLeft => Level(1),
|
||||
ParagraphDirectionHint::AutoLeftToRight => paragraph_level(&self.char_types, false),
|
||||
ParagraphDirectionHint::AutoLeftToRight => {
|
||||
paragraph_level(&self.char_types, false, Direction::LeftToRight)
|
||||
}
|
||||
ParagraphDirectionHint::AutoRightToLeft => {
|
||||
paragraph_level(&self.char_types, false, Direction::RightToLeft)
|
||||
}
|
||||
};
|
||||
|
||||
self.dump_state("before X1-X8");
|
||||
@ -1373,7 +1405,8 @@ impl BidiContext {
|
||||
}
|
||||
// X5c
|
||||
BidiClass::FirstStrongIsolate => {
|
||||
let level = paragraph_level(&self.char_types[idx + 1..], true);
|
||||
let level =
|
||||
paragraph_level(&self.char_types[idx + 1..], true, Direction::LeftToRight);
|
||||
self.levels[idx] = stack.embedding_level();
|
||||
stack.apply_override(&mut self.char_types[idx]);
|
||||
let level = if level.0 == 1 {
|
||||
@ -1802,7 +1835,7 @@ fn span_one_run(types: &[BidiClass], levels: &[Level], start: usize) -> (Level,
|
||||
/// 3.3.1 Paragraph level.
|
||||
/// We've been fed a single paragraph, which takes care of rule P1.
|
||||
/// This function implements rules P2 and P3.
|
||||
fn paragraph_level(types: &[BidiClass], respect_pdi: bool) -> Level {
|
||||
fn paragraph_level(types: &[BidiClass], respect_pdi: bool, fallback: Direction) -> Level {
|
||||
let mut isolate_count = 0;
|
||||
for &t in types {
|
||||
match t {
|
||||
@ -1813,18 +1846,21 @@ fn paragraph_level(types: &[BidiClass], respect_pdi: bool) -> Level {
|
||||
if isolate_count > 0 {
|
||||
isolate_count -= 1;
|
||||
} else if respect_pdi {
|
||||
return Level(0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
BidiClass::LeftToRight if isolate_count == 0 => return Level(0),
|
||||
|
||||
BidiClass::RightToLeft | BidiClass::ArabicLetter if isolate_count == 0 => {
|
||||
return Level(1)
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Level(0)
|
||||
if fallback == Direction::LeftToRight {
|
||||
Level(0)
|
||||
} else {
|
||||
Level(1)
|
||||
}
|
||||
}
|
||||
|
||||
struct Pair {
|
||||
|
@ -40,6 +40,7 @@ termwiz = { path = "../termwiz" }
|
||||
toml = "0.5"
|
||||
umask = { path = "../umask" }
|
||||
unicode-segmentation = "1.8"
|
||||
wezterm-bidi = { path = "../bidi", features=["use_serde"] }
|
||||
wezterm-input-types = { path = "../wezterm-input-types" }
|
||||
wezterm-term = { path = "../term", features=["use_serde"] }
|
||||
|
||||
|
@ -31,6 +31,7 @@ use std::sync::atomic::Ordering;
|
||||
use std::time::Duration;
|
||||
use termwiz::hyperlink;
|
||||
use termwiz::surface::CursorShape;
|
||||
use wezterm_bidi::ParagraphDirectionHint;
|
||||
use wezterm_input_types::{KeyCode, Modifiers, WindowDecorations};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
@ -535,7 +536,10 @@ pub struct Config {
|
||||
pub experimental_pixel_positioning: bool,
|
||||
|
||||
#[serde(default)]
|
||||
pub experimental_bidi: bool,
|
||||
pub bidi_enabled: bool,
|
||||
|
||||
#[serde(default)]
|
||||
pub bidi_direction: ParagraphDirectionHint,
|
||||
|
||||
#[serde(default = "default_stateless_process_list")]
|
||||
pub skip_close_confirmation_for_processes_named: Vec<String>,
|
||||
|
@ -4,6 +4,7 @@ use crate::{configuration, ConfigHandle, NewlineCanon};
|
||||
use std::sync::Mutex;
|
||||
use termwiz::hyperlink::Rule as HyperlinkRule;
|
||||
use wezterm_term::color::ColorPalette;
|
||||
use wezterm_term::config::BidiMode;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TermConfig {
|
||||
@ -92,4 +93,12 @@ impl wezterm_term::TerminalConfiguration for TermConfig {
|
||||
fn debug_key_events(&self) -> bool {
|
||||
self.configuration().debug_key_events
|
||||
}
|
||||
|
||||
fn bidi_mode(&self) -> BidiMode {
|
||||
let config = self.configuration();
|
||||
BidiMode {
|
||||
enabled: config.bidi_enabled,
|
||||
hint: config.bidi_direction,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ keywords = ["terminal", "emulator", "vte"]
|
||||
readme = "README.md"
|
||||
|
||||
[features]
|
||||
use_serde = ["termwiz/use_serde"]
|
||||
use_serde = ["termwiz/use_serde", "wezterm-bidi/use_serde"]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
@ -30,6 +30,7 @@ terminfo = "0.7"
|
||||
unicode-segmentation = "1.8"
|
||||
unicode-width = "0.1"
|
||||
url = "2"
|
||||
wezterm-bidi = { path = "../bidi" }
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "0.6"
|
||||
|
@ -1,5 +1,7 @@
|
||||
use crate::color::ColorPalette;
|
||||
use termwiz::hyperlink::Rule as HyperlinkRule;
|
||||
use termwiz::surface::{Line, SequenceNo};
|
||||
use wezterm_bidi::ParagraphDirectionHint;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum NewlineCanon {
|
||||
@ -222,4 +224,25 @@ pub trait TerminalConfiguration: std::fmt::Debug {
|
||||
fn debug_key_events(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Returns (bidi_enabled, direction hint) that should be used
|
||||
/// unless an escape sequence has changed the default mode
|
||||
fn bidi_mode(&self) -> BidiMode {
|
||||
BidiMode {
|
||||
enabled: false,
|
||||
hint: ParagraphDirectionHint::LeftToRight,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct BidiMode {
|
||||
pub enabled: bool,
|
||||
pub hint: ParagraphDirectionHint,
|
||||
}
|
||||
|
||||
impl BidiMode {
|
||||
pub fn apply_to_line(&self, line: &mut Line, seqno: SequenceNo) {
|
||||
line.set_bidi_info(self.enabled, self.hint, seqno);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
#![cfg_attr(feature = "cargo-clippy", allow(clippy::range_plus_one))]
|
||||
use super::*;
|
||||
use crate::config::BidiMode;
|
||||
use log::debug;
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::Arc;
|
||||
@ -57,6 +58,7 @@ impl Screen {
|
||||
config: &Arc<dyn TerminalConfiguration>,
|
||||
allow_scrollback: bool,
|
||||
seqno: SequenceNo,
|
||||
bidi_mode: BidiMode,
|
||||
) -> Screen {
|
||||
let physical_rows = physical_rows.max(1);
|
||||
let physical_cols = physical_cols.max(1);
|
||||
@ -64,7 +66,9 @@ impl Screen {
|
||||
let mut lines =
|
||||
VecDeque::with_capacity(physical_rows + scrollback_size(config, allow_scrollback));
|
||||
for _ in 0..physical_rows {
|
||||
lines.push_back(Line::with_width(physical_cols, seqno));
|
||||
let mut line = Line::with_width(physical_cols, seqno);
|
||||
bidi_mode.apply_to_line(&mut line, seqno);
|
||||
lines.push_back(line);
|
||||
}
|
||||
|
||||
Screen {
|
||||
@ -216,6 +220,7 @@ impl Screen {
|
||||
// lines than the viewport size, or we resized taller,
|
||||
// pad us back out to the viewport size
|
||||
while self.lines.len() < physical_rows {
|
||||
// FIXME: borrow bidi mode from line
|
||||
self.lines
|
||||
.push_back(Line::with_width(self.physical_cols, seqno));
|
||||
}
|
||||
@ -238,6 +243,7 @@ impl Screen {
|
||||
physical_rows.saturating_sub(new_cursor_y as usize);
|
||||
let actual_num_rows_after_cursor = self.lines.len().saturating_sub(cursor_y);
|
||||
for _ in actual_num_rows_after_cursor..required_num_rows_after_cursor {
|
||||
// FIXME: borrow bidi mode from line
|
||||
self.lines
|
||||
.push_back(Line::with_width(self.physical_cols, seqno));
|
||||
}
|
||||
@ -363,9 +369,13 @@ impl Screen {
|
||||
cols: Range<usize>,
|
||||
attr: &CellAttributes,
|
||||
seqno: SequenceNo,
|
||||
bidi_mode: BidiMode,
|
||||
) {
|
||||
let line_idx = self.phys_row(y);
|
||||
let line = self.line_mut(line_idx);
|
||||
if cols.start == 0 {
|
||||
bidi_mode.apply_to_line(line, seqno);
|
||||
}
|
||||
line.fill_range(cols, &Cell::blank_with_attrs(attr.clone()), seqno);
|
||||
}
|
||||
|
||||
@ -460,6 +470,7 @@ impl Screen {
|
||||
num_rows: usize,
|
||||
seqno: SequenceNo,
|
||||
blank_attr: CellAttributes,
|
||||
bidi_mode: BidiMode,
|
||||
) {
|
||||
log::debug!(
|
||||
"scroll_up_within_margins region:{:?} margins:{:?} rows={}",
|
||||
@ -469,7 +480,7 @@ impl Screen {
|
||||
);
|
||||
|
||||
if left_and_right_margins.start == 0 && left_and_right_margins.end == self.physical_cols {
|
||||
return self.scroll_up(scroll_region, num_rows, seqno, blank_attr);
|
||||
return self.scroll_up(scroll_region, num_rows, seqno, blank_attr, bidi_mode);
|
||||
}
|
||||
|
||||
// Need to do the slower, more complex left and right bounded scroll
|
||||
@ -557,6 +568,7 @@ impl Screen {
|
||||
num_rows: usize,
|
||||
seqno: SequenceNo,
|
||||
blank_attr: CellAttributes,
|
||||
bidi_mode: BidiMode,
|
||||
) {
|
||||
let phys_scroll = self.phys_range(scroll_region);
|
||||
let num_rows = num_rows.min(phys_scroll.end - phys_scroll.start);
|
||||
@ -627,25 +639,19 @@ impl Screen {
|
||||
self.stable_row_index_offset += lines_removed;
|
||||
}
|
||||
|
||||
if scroll_region.end as usize == self.physical_rows {
|
||||
// It's cheaper to push() than it is insert() at the end
|
||||
for _ in 0..to_add {
|
||||
self.lines.push_back(Line::with_width_and_cell(
|
||||
self.physical_cols,
|
||||
Cell::blank_with_attrs(blank_attr.clone()),
|
||||
seqno,
|
||||
));
|
||||
}
|
||||
} else {
|
||||
for _ in 0..to_add {
|
||||
self.lines.insert(
|
||||
phys_scroll.end,
|
||||
Line::with_width_and_cell(
|
||||
self.physical_cols,
|
||||
Cell::blank_with_attrs(blank_attr.clone()),
|
||||
seqno,
|
||||
),
|
||||
);
|
||||
// It's cheaper to push() than it is insert() at the end
|
||||
let push = scroll_region.end as usize == self.physical_rows;
|
||||
for _ in 0..to_add {
|
||||
let mut line = Line::with_width_and_cell(
|
||||
self.physical_cols,
|
||||
Cell::blank_with_attrs(blank_attr.clone()),
|
||||
seqno,
|
||||
);
|
||||
bidi_mode.apply_to_line(&mut line, seqno);
|
||||
if push {
|
||||
self.lines.push_back(line);
|
||||
} else {
|
||||
self.lines.insert(phys_scroll.end, line);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -677,6 +683,7 @@ impl Screen {
|
||||
num_rows: usize,
|
||||
seqno: SequenceNo,
|
||||
blank_attr: CellAttributes,
|
||||
bidi_mode: BidiMode,
|
||||
) {
|
||||
debug!("scroll_down {:?} {}", scroll_region, num_rows);
|
||||
let phys_scroll = self.phys_range(scroll_region);
|
||||
@ -694,14 +701,13 @@ impl Screen {
|
||||
}
|
||||
|
||||
for _ in 0..num_rows {
|
||||
self.lines.insert(
|
||||
phys_scroll.start,
|
||||
Line::with_width_and_cell(
|
||||
self.physical_cols,
|
||||
Cell::blank_with_attrs(blank_attr.clone()),
|
||||
seqno,
|
||||
),
|
||||
let mut line = Line::with_width_and_cell(
|
||||
self.physical_cols,
|
||||
Cell::blank_with_attrs(blank_attr.clone()),
|
||||
seqno,
|
||||
);
|
||||
bidi_mode.apply_to_line(&mut line, seqno);
|
||||
self.lines.insert(phys_scroll.start, line);
|
||||
}
|
||||
}
|
||||
|
||||
@ -712,9 +718,10 @@ impl Screen {
|
||||
num_rows: usize,
|
||||
seqno: SequenceNo,
|
||||
blank_attr: CellAttributes,
|
||||
bidi_mode: BidiMode,
|
||||
) {
|
||||
if left_and_right_margins.start == 0 && left_and_right_margins.end == self.physical_cols {
|
||||
return self.scroll_down(scroll_region, num_rows, seqno, blank_attr);
|
||||
return self.scroll_down(scroll_region, num_rows, seqno, blank_attr, bidi_mode);
|
||||
}
|
||||
|
||||
// Need to do the slower, more complex left and right bounded scroll
|
||||
|
@ -3,7 +3,7 @@
|
||||
#![cfg_attr(feature = "cargo-clippy", allow(clippy::range_plus_one))]
|
||||
use super::*;
|
||||
use crate::color::{ColorPalette, RgbColor};
|
||||
use crate::config::NewlineCanon;
|
||||
use crate::config::{BidiMode, NewlineCanon};
|
||||
use log::debug;
|
||||
use num_traits::ToPrimitive;
|
||||
use std::collections::HashMap;
|
||||
@ -21,6 +21,7 @@ use termwiz::image::ImageData;
|
||||
use termwiz::input::KeyboardEncoding;
|
||||
use termwiz::surface::{CursorShape, CursorVisibility, SequenceNo};
|
||||
use url::Url;
|
||||
use wezterm_bidi::ParagraphDirectionHint;
|
||||
|
||||
mod image;
|
||||
mod iterm;
|
||||
@ -172,9 +173,17 @@ impl ScreenOrAlt {
|
||||
physical_cols: usize,
|
||||
config: &Arc<dyn TerminalConfiguration>,
|
||||
seqno: SequenceNo,
|
||||
bidi_mode: BidiMode,
|
||||
) -> Self {
|
||||
let screen = Screen::new(physical_rows, physical_cols, config, true, seqno);
|
||||
let alt_screen = Screen::new(physical_rows, physical_cols, config, false, seqno);
|
||||
let screen = Screen::new(physical_rows, physical_cols, config, true, seqno, bidi_mode);
|
||||
let alt_screen = Screen::new(
|
||||
physical_rows,
|
||||
physical_cols,
|
||||
config,
|
||||
false,
|
||||
seqno,
|
||||
bidi_mode,
|
||||
);
|
||||
|
||||
Self {
|
||||
screen,
|
||||
@ -369,6 +378,17 @@ pub struct TerminalState {
|
||||
|
||||
lost_focus_seqno: SequenceNo,
|
||||
focused: bool,
|
||||
|
||||
/// True if lines should be marked as bidi-enabled, and thus
|
||||
/// have the renderer apply the bidi algorithm.
|
||||
/// true is equivalent to "implicit" bidi mode as described in
|
||||
/// <https://terminal-wg.pages.freedesktop.org/bidi/recommendation/basic-modes.html>
|
||||
/// If none, then the default value specified by the config is used.
|
||||
bidi_enabled: Option<bool>,
|
||||
/// When set, specifies the bidi direction information that should be
|
||||
/// applied to lines.
|
||||
/// If none, then the default value specified by the config is used.
|
||||
bidi_hint: Option<ParagraphDirectionHint>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -445,7 +465,13 @@ impl TerminalState {
|
||||
) -> TerminalState {
|
||||
let writer = Box::new(ThreadedWriter::new(writer));
|
||||
let seqno = 1;
|
||||
let screen = ScreenOrAlt::new(size.physical_rows, size.physical_cols, &config, seqno);
|
||||
let screen = ScreenOrAlt::new(
|
||||
size.physical_rows,
|
||||
size.physical_cols,
|
||||
&config,
|
||||
seqno,
|
||||
config.bidi_mode(),
|
||||
);
|
||||
|
||||
let color_map = default_color_map();
|
||||
|
||||
@ -513,6 +539,8 @@ impl TerminalState {
|
||||
accumulating_title: None,
|
||||
lost_focus_seqno: seqno,
|
||||
focused: true,
|
||||
bidi_enabled: None,
|
||||
bidi_hint: None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -914,12 +942,14 @@ impl TerminalState {
|
||||
let blank_attr = self.pen.clone_sgr_only();
|
||||
let top_and_bottom_margins = self.top_and_bottom_margins.clone();
|
||||
let left_and_right_margins = self.left_and_right_margins.clone();
|
||||
let bidi_mode = self.get_bidi_mode();
|
||||
self.screen_mut().scroll_up_within_margins(
|
||||
&top_and_bottom_margins,
|
||||
&left_and_right_margins,
|
||||
num_rows,
|
||||
seqno,
|
||||
blank_attr,
|
||||
bidi_mode,
|
||||
)
|
||||
}
|
||||
|
||||
@ -928,12 +958,14 @@ impl TerminalState {
|
||||
let blank_attr = self.pen.clone_sgr_only();
|
||||
let top_and_bottom_margins = self.top_and_bottom_margins.clone();
|
||||
let left_and_right_margins = self.left_and_right_margins.clone();
|
||||
let bidi_mode = self.get_bidi_mode();
|
||||
self.screen_mut().scroll_down_within_margins(
|
||||
&top_and_bottom_margins,
|
||||
&left_and_right_margins,
|
||||
num_rows,
|
||||
seqno,
|
||||
blank_attr,
|
||||
bidi_mode,
|
||||
)
|
||||
}
|
||||
|
||||
@ -1130,6 +1162,8 @@ impl TerminalState {
|
||||
|
||||
self.reverse_wraparound_mode = false;
|
||||
self.reverse_video_mode = false;
|
||||
self.bidi_enabled.take();
|
||||
self.bidi_hint.take();
|
||||
}
|
||||
Device::RequestPrimaryDeviceAttributes => {
|
||||
let mut ident = "\x1b[?65".to_string(); // Vt500
|
||||
@ -1421,6 +1455,21 @@ impl TerminalState {
|
||||
self.erase_in_display(EraseInDisplay::EraseDisplay);
|
||||
}
|
||||
|
||||
Mode::SetMode(TerminalMode::Code(TerminalModeCode::BiDirectionalSupportMode)) => {
|
||||
self.bidi_enabled.replace(true);
|
||||
}
|
||||
Mode::ResetMode(TerminalMode::Code(TerminalModeCode::BiDirectionalSupportMode)) => {
|
||||
self.bidi_enabled.replace(false);
|
||||
}
|
||||
Mode::QueryMode(TerminalMode::Code(TerminalModeCode::BiDirectionalSupportMode)) => {
|
||||
self.decqrm_response(
|
||||
mode,
|
||||
true,
|
||||
self.bidi_enabled
|
||||
.unwrap_or_else(|| self.config.bidi_mode().enabled),
|
||||
);
|
||||
}
|
||||
|
||||
Mode::SetMode(TerminalMode::Code(TerminalModeCode::Insert)) => {
|
||||
self.insert = true;
|
||||
}
|
||||
@ -1834,13 +1883,25 @@ impl TerminalState {
|
||||
};
|
||||
|
||||
{
|
||||
let bidi_mode = self.get_bidi_mode();
|
||||
let screen = self.screen_mut();
|
||||
for y in row_range.clone() {
|
||||
screen.clear_line(y, col_range.clone(), &pen, seqno);
|
||||
screen.clear_line(y, col_range.clone(), &pen, seqno, bidi_mode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_bidi_mode(&self) -> BidiMode {
|
||||
let mut mode = self.config.bidi_mode();
|
||||
if let Some(enabled) = &self.bidi_enabled {
|
||||
mode.enabled = *enabled;
|
||||
}
|
||||
if let Some(hint) = &self.bidi_hint {
|
||||
mode.hint = *hint;
|
||||
}
|
||||
mode
|
||||
}
|
||||
|
||||
fn perform_csi_edit(&mut self, edit: Edit) {
|
||||
let seqno = self.seqno;
|
||||
match edit {
|
||||
@ -1866,12 +1927,14 @@ impl TerminalState {
|
||||
let top_and_bottom_margins = self.cursor.y..self.top_and_bottom_margins.end;
|
||||
let left_and_right_margins = self.left_and_right_margins.clone();
|
||||
let blank_attr = self.pen.clone_sgr_only();
|
||||
let bidi_mode = self.get_bidi_mode();
|
||||
self.screen_mut().scroll_up_within_margins(
|
||||
&top_and_bottom_margins,
|
||||
&left_and_right_margins,
|
||||
n as usize,
|
||||
seqno,
|
||||
blank_attr,
|
||||
bidi_mode,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1893,13 +1956,15 @@ impl TerminalState {
|
||||
let cy = self.cursor.y;
|
||||
let pen = self.pen.clone_sgr_only();
|
||||
let cols = self.screen().physical_cols;
|
||||
let bidi_mode = self.get_bidi_mode();
|
||||
let range = match erase {
|
||||
EraseInLine::EraseToEndOfLine => cx..cols,
|
||||
EraseInLine::EraseToStartOfLine => 0..cx + 1,
|
||||
EraseInLine::EraseLine => 0..cols,
|
||||
};
|
||||
|
||||
self.screen_mut().clear_line(cy, range.clone(), &pen, seqno);
|
||||
self.screen_mut()
|
||||
.clear_line(cy, range.clone(), &pen, seqno, bidi_mode);
|
||||
}
|
||||
Edit::InsertCharacter(n) => {
|
||||
// https://vt100.net/docs/vt510-rm/ICH.html
|
||||
@ -1924,6 +1989,7 @@ impl TerminalState {
|
||||
if self.top_and_bottom_margins.contains(&self.cursor.y)
|
||||
&& self.left_and_right_margins.contains(&self.cursor.x)
|
||||
{
|
||||
let bidi_mode = self.get_bidi_mode();
|
||||
let top_and_bottom_margins = self.cursor.y..self.top_and_bottom_margins.end;
|
||||
let left_and_right_margins = self.left_and_right_margins.clone();
|
||||
let blank_attr = self.pen.clone_sgr_only();
|
||||
@ -1933,6 +1999,7 @@ impl TerminalState {
|
||||
n as usize,
|
||||
seqno,
|
||||
blank_attr,
|
||||
bidi_mode,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ use num_traits::FromPrimitive;
|
||||
use std::fmt::Write;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use termwiz::cell::{grapheme_column_width, Cell, CellAttributes, SemanticType, UnicodeVersion};
|
||||
use termwiz::escape::csi::EraseInDisplay;
|
||||
use termwiz::escape::csi::{CharacterPath, EraseInDisplay};
|
||||
use termwiz::escape::osc::{
|
||||
ChangeColorPair, ColorOrQuery, FinalTermSemanticPrompt, ITermProprietary,
|
||||
ITermUnicodeVersionOp, Selection,
|
||||
@ -18,6 +18,7 @@ use termwiz::escape::{
|
||||
};
|
||||
use termwiz::input::KeyboardEncoding;
|
||||
use url::Url;
|
||||
use wezterm_bidi::ParagraphDirectionHint;
|
||||
|
||||
/// A helper struct for implementing `vtparse::VTActor` while compartmentalizing
|
||||
/// the terminal state and the embedding/host terminal interface
|
||||
@ -412,8 +413,18 @@ impl<'a> Performer<'a> {
|
||||
CSI::Device(dev) => self.state.perform_device(*dev),
|
||||
CSI::Mouse(mouse) => error!("mouse report sent by app? {:?}", mouse),
|
||||
CSI::Window(window) => self.state.perform_csi_window(window),
|
||||
CSI::SelectCharacterPath(path, _) => {
|
||||
log::warn!("unhandled SelectCharacterPath {:?}", path);
|
||||
CSI::SelectCharacterPath(CharacterPath::ImplementationDefault, _) => {
|
||||
self.state.bidi_hint.take();
|
||||
}
|
||||
CSI::SelectCharacterPath(CharacterPath::LeftToRightOrTopToBottom, _) => {
|
||||
self.state
|
||||
.bidi_hint
|
||||
.replace(ParagraphDirectionHint::LeftToRight);
|
||||
}
|
||||
CSI::SelectCharacterPath(CharacterPath::RightToLeftOrBottomToTop, _) => {
|
||||
self.state
|
||||
.bidi_hint
|
||||
.replace(ParagraphDirectionHint::RightToLeft);
|
||||
}
|
||||
CSI::Unspecified(unspec) => {
|
||||
log::warn!("unknown unspecified CSI: {:?}", format!("{}", unspec))
|
||||
|
@ -8,13 +8,12 @@ use serde::{Deserialize, Serialize};
|
||||
use std::ops::Range;
|
||||
use std::sync::Arc;
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
use wezterm_bidi::ParagraphDirectionHint;
|
||||
use wezterm_bidi::{Direction, ParagraphDirectionHint};
|
||||
|
||||
bitflags! {
|
||||
#[cfg_attr(feature="use_serde", derive(Serialize, Deserialize))]
|
||||
struct LineBits : u8 {
|
||||
struct LineBits : u16 {
|
||||
const NONE = 0;
|
||||
const _UNUSED = 1;
|
||||
/// The line contains 1+ cells with explicit hyperlinks set
|
||||
const HAS_HYPERLINK = 1<<1;
|
||||
/// true if we have scanned for implicit hyperlinks
|
||||
@ -43,6 +42,24 @@ bitflags! {
|
||||
Self::DOUBLE_HEIGHT_TOP.bits |
|
||||
Self::DOUBLE_HEIGHT_BOTTOM.bits;
|
||||
|
||||
/// true if the line should have the bidi algorithm
|
||||
/// applied as part of presentation.
|
||||
/// This corresponds to the "implicit" bidi modes
|
||||
/// described in
|
||||
/// <https://terminal-wg.pages.freedesktop.org/bidi/recommendation/basic-modes.html>
|
||||
const BIDI_ENABLED = 1<<0;
|
||||
|
||||
/// true if the line base direction is RTL.
|
||||
/// When BIDI_ENABLED is also true, this is passed to the bidi algorithm.
|
||||
/// When rendering, the line will be rendered from RTL.
|
||||
const RTL = 1<<8;
|
||||
|
||||
/// true if the direction for the line should be auto-detected
|
||||
/// when BIDI_ENABLED is also true.
|
||||
/// If false, the direction is taken from the RTL bit only.
|
||||
/// Otherwise, the auto-detect direction is used, falling back
|
||||
/// to the direction specified by the RTL bit.
|
||||
const AUTO_DETECT_DIRECTION = 1<<9;
|
||||
}
|
||||
}
|
||||
|
||||
@ -292,6 +309,61 @@ impl Line {
|
||||
self.update_last_change_seqno(seqno);
|
||||
}
|
||||
|
||||
/// Set a flag the indicate whether the line should have the bidi
|
||||
/// algorithm applied during rendering
|
||||
pub fn set_bidi_enabled(&mut self, enabled: bool, seqno: SequenceNo) {
|
||||
self.bits.set(LineBits::BIDI_ENABLED, enabled);
|
||||
self.update_last_change_seqno(seqno);
|
||||
}
|
||||
|
||||
/// Set the bidi direction for the line.
|
||||
/// This affects both the bidi algorithm (if enabled via set_bidi_enabled)
|
||||
/// and the layout direction of the line.
|
||||
/// `auto_detect` specifies whether the direction should be auto-detected
|
||||
/// before falling back to the specified direction.
|
||||
pub fn set_direction(&mut self, direction: Direction, auto_detect: bool, seqno: SequenceNo) {
|
||||
self.bits
|
||||
.set(LineBits::RTL, direction == Direction::LeftToRight);
|
||||
self.bits.set(LineBits::AUTO_DETECT_DIRECTION, auto_detect);
|
||||
self.update_last_change_seqno(seqno);
|
||||
}
|
||||
|
||||
pub fn set_bidi_info(
|
||||
&mut self,
|
||||
enabled: bool,
|
||||
direction: ParagraphDirectionHint,
|
||||
seqno: SequenceNo,
|
||||
) {
|
||||
self.bits.set(LineBits::BIDI_ENABLED, enabled);
|
||||
let (auto, rtl) = match direction {
|
||||
ParagraphDirectionHint::AutoRightToLeft => (true, true),
|
||||
ParagraphDirectionHint::AutoLeftToRight => (true, false),
|
||||
ParagraphDirectionHint::LeftToRight => (false, false),
|
||||
ParagraphDirectionHint::RightToLeft => (false, true),
|
||||
};
|
||||
self.bits.set(LineBits::AUTO_DETECT_DIRECTION, auto);
|
||||
self.bits.set(LineBits::RTL, rtl);
|
||||
self.update_last_change_seqno(seqno);
|
||||
}
|
||||
|
||||
/// Returns a tuple of (BIDI_ENABLED, Direction), indicating whether
|
||||
/// the line should have the bidi algorithm applied and its base
|
||||
/// direction, respectively.
|
||||
pub fn bidi_info(&self) -> (bool, ParagraphDirectionHint) {
|
||||
(
|
||||
self.bits.contains(LineBits::BIDI_ENABLED),
|
||||
match (
|
||||
self.bits.contains(LineBits::AUTO_DETECT_DIRECTION),
|
||||
self.bits.contains(LineBits::RTL),
|
||||
) {
|
||||
(true, true) => ParagraphDirectionHint::AutoRightToLeft,
|
||||
(false, true) => ParagraphDirectionHint::RightToLeft,
|
||||
(true, false) => ParagraphDirectionHint::AutoLeftToRight,
|
||||
(false, false) => ParagraphDirectionHint::LeftToRight,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn invalidate_zones(&mut self) {
|
||||
self.zones.clear();
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ use std::sync::Arc;
|
||||
use structopt::StructOpt;
|
||||
use termwiz::cell::CellAttributes;
|
||||
use termwiz::surface::{Line, SEQ_ZERO};
|
||||
use wezterm_bidi::{Direction, ParagraphDirectionHint};
|
||||
use wezterm_bidi::Direction;
|
||||
use wezterm_client::domain::{ClientDomain, ClientDomainConfig};
|
||||
use wezterm_gui_subcommands::*;
|
||||
use wezterm_toast_notification::*;
|
||||
@ -675,8 +675,8 @@ pub fn run_ls_fonts(config: config::ConfigHandle, cmd: &LsFontsCommand) -> anyho
|
||||
config.dpi.unwrap_or_else(|| ::window::default_dpi()) as usize,
|
||||
)?;
|
||||
|
||||
let bidi_hint = if config.experimental_bidi {
|
||||
Some(ParagraphDirectionHint::LeftToRight)
|
||||
let bidi_hint = if config.bidi_enabled {
|
||||
Some(config.bidi_direction)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
@ -33,7 +33,6 @@ use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
|
||||
use termwiz::cell::{unicode_column_width, Blink};
|
||||
use termwiz::cellcluster::CellCluster;
|
||||
use termwiz::surface::{CursorShape, CursorVisibility};
|
||||
use wezterm_bidi::ParagraphDirectionHint;
|
||||
use wezterm_font::units::{IntPixelLength, PixelLength};
|
||||
use wezterm_font::{ClearShapeCache, GlyphInfo, LoadedFont};
|
||||
use wezterm_term::color::{ColorAttribute, ColorPalette, RgbColor};
|
||||
@ -1760,8 +1759,9 @@ impl super::TermWindow {
|
||||
|
||||
let mut composition_width = 0;
|
||||
|
||||
let bidi_hint = if self.config.experimental_bidi {
|
||||
Some(ParagraphDirectionHint::LeftToRight)
|
||||
let (bidi_enabled, bidi_direction) = params.line.bidi_info();
|
||||
let bidi_hint = if bidi_enabled {
|
||||
Some(bidi_direction)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user