1
1
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:
Wez Furlong 2022-01-30 09:22:17 -07:00
parent 66b227bbf9
commit 98f35bbf24
14 changed files with 293 additions and 55 deletions

3
Cargo.lock generated
View File

@ -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]]

View File

@ -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"

View File

@ -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 {

View File

@ -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"] }

View File

@ -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>,

View File

@ -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,
}
}
}

View File

@ -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"

View File

@ -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);
}
}

View File

@ -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

View File

@ -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,
);
}
}

View File

@ -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))

View File

@ -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();
}

View File

@ -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
};

View File

@ -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
};