Support terminal-wg's BiDi draft proposal in alacritty_terminal
https://terminal-wg.pages.freedesktop.org/bidi Implementation is hidden behind a `bidi_draft` crate feature, and depends on alacritty/vte#112. Public API is limited to: * `Cell::bidi_mode()` method which returns a `BidiMode` enum value. * `Cell::bidi_box_mirroring()` method which returns a boolean. * `Term` boolean field `bidi_disable_arrow_key_swapping`. Cell `BidiFlags` is only used internally since relations/interactions between individual flags are not straight forward, and could lead to erroneous behavior if it's all left for API consumers to figure out. `BidiDir` named fields exist in all `BidiMode` variants. This is done deliberately (instead of e.g. a `BidiMode` struct with a `BidiDir` field) to signify the different purpose `BidiDir` serves in each mode. Signed-off-by: Mohammad AlSaleh <CE.Mohammad.AlSaleh@gmail.com>
This commit is contained in:
parent
9af7eb1b31
commit
2295953a66
|
@ -150,7 +150,7 @@ dependencies = [
|
|||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"utf8parse",
|
||||
"utf8parse 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -165,7 +165,7 @@ version = "0.2.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
"utf8parse 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1841,6 +1841,11 @@ version = "0.2.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.1"
|
||||
source = "git+https://github.com/MoSal/vte?branch=scp#96de2f46d92d6560b0dfb6503f38e41fc85ebdbe"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.4"
|
||||
|
@ -1870,22 +1875,20 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "vte"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40eb22ae96f050e0c0d6f7ce43feeae26c348fc4dea56928ca81537cfaa6188b"
|
||||
source = "git+https://github.com/MoSal/vte?branch=scp#96de2f46d92d6560b0dfb6503f38e41fc85ebdbe"
|
||||
dependencies = [
|
||||
"bitflags 2.4.2",
|
||||
"cursor-icon",
|
||||
"log",
|
||||
"serde",
|
||||
"utf8parse",
|
||||
"utf8parse 0.2.1 (git+https://github.com/MoSal/vte?branch=scp)",
|
||||
"vte_generate_state_changes",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vte_generate_state_changes"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d257817081c7dffcdbab24b9e62d2def62e2ff7d00b1c20062551e6cccc145ff"
|
||||
source = "git+https://github.com/MoSal/vte?branch=scp#96de2f46d92d6560b0dfb6503f38e41fc85ebdbe"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
|
@ -12,6 +12,8 @@ rust-version = "1.70.0"
|
|||
[features]
|
||||
default = ["serde"]
|
||||
serde = ["dep:serde", "bitflags/serde", "vte/serde"]
|
||||
# Support Bidi/RTL draft proposal from https://terminal-wg.pages.freedesktop.org/bidi
|
||||
bidi_draft = []
|
||||
|
||||
[dependencies]
|
||||
base64 = "0.22.0"
|
||||
|
@ -23,7 +25,7 @@ parking_lot = "0.12.0"
|
|||
polling = "3.0.0"
|
||||
regex-automata = "0.4.3"
|
||||
unicode-width = "0.1"
|
||||
vte = { version = "0.13.0", default-features = false, features = ["ansi", "serde"] }
|
||||
vte = { git = "https://github.com/MoSal/vte", branch = "scp" , default-features = false, features = ["ansi", "serde"] }
|
||||
serde = { version = "1", features = ["derive", "rc"], optional = true }
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
|
|
|
@ -36,6 +36,97 @@ bitflags! {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "bidi_draft")]
|
||||
bitflags! {
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub(super) struct BidiFlags: u8 {
|
||||
/// Set with:
|
||||
/// `CSI 8 l`
|
||||
///
|
||||
/// Reset with:
|
||||
/// `CSI 8 h`
|
||||
///
|
||||
/// Yes, the default is the high state (implicit mode).
|
||||
const EXPLICIT_DIRECTION = 0b0000_0001;
|
||||
/// Set with:
|
||||
/// `CSI 2 SPACE k` (RTL).
|
||||
/// `CSI 1 SPACE k` (LTR)
|
||||
///
|
||||
/// Reset with:
|
||||
/// `CSI 0 SPACE k` (default)
|
||||
/// `CSI SPACE k` (default)
|
||||
///
|
||||
/// Default paragraph direction is not to be confused with
|
||||
/// auto-detection. The default is implementation defined, and
|
||||
/// is often LTR.
|
||||
const NON_DEFAULT_PARA_DIR = 0b0000_0010;
|
||||
/// Set with:
|
||||
/// `CSI 2 SPACE k` (RTL)
|
||||
///
|
||||
/// Reset with with:
|
||||
/// `CSI 1 SPACE k` (LTR)
|
||||
///
|
||||
/// Only in effect when `NON_DEFAULT_PARA_DIR` is set.
|
||||
/// Only acts as fallback when `AUTO_PARA_DIR` is set.
|
||||
const RTL_PARA_DIR = 0b0000_0100;
|
||||
/// Set with:
|
||||
/// `CSI ? 2501 h` (auto)
|
||||
///
|
||||
/// Reset with:
|
||||
/// `CSI ? 2501 l` (default or RTL/LTR)
|
||||
///
|
||||
/// Set auto direction for paragraphs, based on their content's detected direction.
|
||||
/// Implicit paragraph direction is used if no specific direction is detected.
|
||||
/// This is ignored if `EXPLICIT_DIRECTION` is set.
|
||||
const AUTO_PARA_DIR = 0b0000_1000;
|
||||
|
||||
/// Set with:
|
||||
/// `CSI ? 2500 h` (mirroring)
|
||||
///
|
||||
/// Reset with:
|
||||
/// `CSI ? 2500 l` (no mirroring)
|
||||
///
|
||||
/// Use mirrored glyphs of characters from the box drawing block
|
||||
/// (U+2500 - U+257F) in RTL spans.
|
||||
///
|
||||
/// Visually mirror-able characters from that range don't have the `Bidi_Mirrored` property
|
||||
/// set. So a Bidi-aware shaper/renderer wouldn't mirror them on its own when detected in
|
||||
/// RTL spans.
|
||||
///
|
||||
/// More info:
|
||||
/// https://www.unicode.org/reports/tr9/#Mirroring
|
||||
/// https://www.unicode.org/reports/tr9/#HL6
|
||||
///
|
||||
/// Note that this will have an effect in RTL spans, irregardless of paragraph direction.
|
||||
const BOX_MIRRORING = 0b0001_0000;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "bidi_draft")]
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum BidiDir {
|
||||
LTR,
|
||||
RTL,
|
||||
// Terminal-defined
|
||||
Default,
|
||||
}
|
||||
|
||||
#[cfg(feature = "bidi_draft")]
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum BidiMode {
|
||||
Explicit { forced_dir: BidiDir },
|
||||
Implicit { para_dir: BidiDir },
|
||||
Auto { fallback_para_dir: BidiDir },
|
||||
}
|
||||
|
||||
#[cfg(feature = "bidi_draft")]
|
||||
impl Default for BidiMode {
|
||||
fn default() -> Self {
|
||||
Self::Implicit { para_dir: BidiDir::Default }
|
||||
}
|
||||
}
|
||||
|
||||
/// Counter for hyperlinks without explicit ID.
|
||||
static HYPERLINK_ID_SUFFIX: AtomicU32 = AtomicU32::new(0);
|
||||
|
||||
|
@ -128,6 +219,9 @@ pub struct CellExtra {
|
|||
underline_color: Option<Color>,
|
||||
|
||||
hyperlink: Option<Hyperlink>,
|
||||
|
||||
#[cfg(feature = "bidi_draft")]
|
||||
pub(super) bidi_flags: BidiFlags,
|
||||
}
|
||||
|
||||
/// Content and attributes of a single cell in the terminal grid.
|
||||
|
@ -222,6 +316,67 @@ impl Cell {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "bidi_draft")]
|
||||
impl Cell {
|
||||
pub(super) fn remove_bidi_flag(&mut self, bidi_flag: BidiFlags) {
|
||||
self.extra.as_mut().map(|extra| Arc::make_mut(extra).bidi_flags.remove(bidi_flag));
|
||||
let is_default_inner =
|
||||
self.extra.as_deref().map(|extra| *extra == Default::default()).unwrap_or(false);
|
||||
if is_default_inner {
|
||||
self.extra = None;
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn insert_bidi_flag(&mut self, bidi_flag: BidiFlags) {
|
||||
if bidi_flag.is_empty() && self.extra.is_none() {
|
||||
return;
|
||||
}
|
||||
|
||||
let extra = self.extra.get_or_insert(Default::default());
|
||||
Arc::make_mut(extra).bidi_flags.insert(bidi_flag);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) fn set_bidi_flags(&mut self, bidi_flags: BidiFlags) {
|
||||
if bidi_flags.is_empty() && self.extra.is_none() {
|
||||
return;
|
||||
}
|
||||
|
||||
let extra = self.extra.get_or_insert(Default::default());
|
||||
Arc::make_mut(extra).bidi_flags = bidi_flags;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) fn bidi_flags(&self) -> BidiFlags {
|
||||
self.extra.as_ref().map(|extra| extra.bidi_flags).unwrap_or_default()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn bidi_mode(&self) -> BidiMode {
|
||||
let bidi_flags = self.bidi_flags();
|
||||
let dir = if !bidi_flags.contains(BidiFlags::NON_DEFAULT_PARA_DIR) {
|
||||
BidiDir::Default
|
||||
} else if bidi_flags.contains(BidiFlags::RTL_PARA_DIR) {
|
||||
BidiDir::RTL
|
||||
} else {
|
||||
BidiDir::LTR
|
||||
};
|
||||
|
||||
if bidi_flags.contains(BidiFlags::EXPLICIT_DIRECTION) {
|
||||
BidiMode::Explicit { forced_dir: dir }
|
||||
} else if bidi_flags.contains(BidiFlags::AUTO_PARA_DIR) {
|
||||
BidiMode::Auto { fallback_para_dir: dir }
|
||||
} else {
|
||||
BidiMode::Implicit { para_dir: dir }
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn bidi_box_mirroring(&self) -> bool {
|
||||
self.bidi_flags().contains(BidiFlags::BOX_MIRRORING)
|
||||
}
|
||||
}
|
||||
|
||||
impl GridCell for Cell {
|
||||
#[inline]
|
||||
fn is_empty(&self) -> bool {
|
||||
|
|
|
@ -25,6 +25,8 @@ use crate::vte::ansi::{
|
|||
KeyboardModesApplyBehavior, NamedColor, NamedMode, NamedPrivateMode, PrivateMode, Rgb,
|
||||
StandardCharset,
|
||||
};
|
||||
#[cfg(feature = "bidi_draft")]
|
||||
use crate::vte::ansi::{ScpCharPath, ScpUpdateMode};
|
||||
|
||||
pub mod cell;
|
||||
pub mod color;
|
||||
|
@ -274,6 +276,16 @@ pub struct Term<T> {
|
|||
|
||||
pub selection: Option<Selection>,
|
||||
|
||||
#[cfg(feature = "bidi_draft")]
|
||||
/// BiDi-supporting terminal emulators should swap the right and left arrow keys by
|
||||
/// default when in RTL paragraph context. This is done to actually match the physical
|
||||
/// movement of the cursor in RTL text in such contexts.
|
||||
///
|
||||
/// This is a global (private) mode of the terminal. The escape sequence `\e[?1243l`
|
||||
/// disables swapping and sets this field to `true`. The escape sequence `\e[?1243h`
|
||||
/// restores the default, setting this field to `false` again.
|
||||
pub bidi_disable_arrow_key_swapping: bool,
|
||||
|
||||
/// Currently active grid.
|
||||
///
|
||||
/// Tracks the screen buffer currently in use. While the alternate screen buffer is active,
|
||||
|
@ -441,6 +453,8 @@ impl<T> Term<T> {
|
|||
selection: None,
|
||||
damage,
|
||||
config: options,
|
||||
#[cfg(feature = "bidi_draft")]
|
||||
bidi_disable_arrow_key_swapping: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -983,6 +997,9 @@ impl<T> Term<T> {
|
|||
let flags = self.grid.cursor.template.flags;
|
||||
let extra = self.grid.cursor.template.extra.clone();
|
||||
|
||||
#[cfg(feature = "bidi_draft")]
|
||||
let bidi_flags = self.grid.cursor.template.bidi_flags();
|
||||
|
||||
let mut cursor_cell = self.grid.cursor_cell();
|
||||
|
||||
// Clear all related cells when overwriting a fullwidth cell.
|
||||
|
@ -1010,6 +1027,11 @@ impl<T> Term<T> {
|
|||
cursor_cell.bg = bg;
|
||||
cursor_cell.flags = flags;
|
||||
cursor_cell.extra = extra;
|
||||
|
||||
#[cfg(feature = "bidi_draft")]
|
||||
{
|
||||
cursor_cell.set_bidi_flags(bidi_flags);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -1863,6 +1885,16 @@ impl<T: EventListener> Handler for Term<T> {
|
|||
Attr::Reverse => cursor.template.flags.insert(Flags::INVERSE),
|
||||
Attr::CancelReverse => cursor.template.flags.remove(Flags::INVERSE),
|
||||
Attr::Bold => cursor.template.flags.insert(Flags::BOLD),
|
||||
// For maximum compatibility with VTE's BiDi implementation, use `CancelBold`
|
||||
// attribute as another way to set `DoubleUnderline`, which happens to be
|
||||
// the behavior defined in ECMA-48 anyway.
|
||||
#[cfg(feature = "bidi_draft")]
|
||||
Attr::CancelBold => {
|
||||
cursor.template.flags.remove(Flags::ALL_UNDERLINES);
|
||||
cursor.template.flags.insert(Flags::DOUBLE_UNDERLINE);
|
||||
},
|
||||
// Keep `CancelBold` behavior if "bidi_draft" feature is not enabled.
|
||||
#[cfg(not(feature = "bidi_draft"))]
|
||||
Attr::CancelBold => cursor.template.flags.remove(Flags::BOLD),
|
||||
Attr::Dim => cursor.template.flags.insert(Flags::DIM),
|
||||
Attr::CancelBoldDim => cursor.template.flags.remove(Flags::BOLD | Flags::DIM),
|
||||
|
@ -1899,11 +1931,62 @@ impl<T: EventListener> Handler for Term<T> {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "bidi_draft")]
|
||||
#[inline]
|
||||
fn set_scp(&mut self, char_path: ScpCharPath, update_mode: ScpUpdateMode) {
|
||||
use cell::BidiFlags;
|
||||
|
||||
if update_mode != ScpUpdateMode::ImplementationDependant {
|
||||
debug!(
|
||||
"Only SCP with zero/default second parameter is supported, as the BiDi draft only \
|
||||
operates in the data component."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
match char_path {
|
||||
ScpCharPath::Default => {
|
||||
trace!("Setting (implementation-defined) default paragraph direction");
|
||||
self.grid.cursor.template.remove_bidi_flag(BidiFlags::NON_DEFAULT_PARA_DIR);
|
||||
},
|
||||
ScpCharPath::LTR => {
|
||||
trace!("Setting LTR paragraph direction");
|
||||
self.grid.cursor.template.insert_bidi_flag(BidiFlags::NON_DEFAULT_PARA_DIR);
|
||||
self.grid.cursor.template.remove_bidi_flag(BidiFlags::RTL_PARA_DIR);
|
||||
},
|
||||
ScpCharPath::RTL => {
|
||||
trace!("Setting RTL paragraph direction");
|
||||
self.grid.cursor.template.insert_bidi_flag(BidiFlags::NON_DEFAULT_PARA_DIR);
|
||||
self.grid.cursor.template.insert_bidi_flag(BidiFlags::RTL_PARA_DIR);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_private_mode(&mut self, mode: PrivateMode) {
|
||||
let mode = match mode {
|
||||
PrivateMode::Named(mode) => mode,
|
||||
PrivateMode::Unknown(mode) => {
|
||||
#[cfg(feature = "bidi_draft")]
|
||||
{
|
||||
use cell::BidiFlags;
|
||||
match mode {
|
||||
1243 => {
|
||||
trace!("Re-enabling RTL arrow key swapping");
|
||||
self.bidi_disable_arrow_key_swapping = false;
|
||||
},
|
||||
2500 => {
|
||||
trace!("Enabling RTL box mirroring");
|
||||
self.grid.cursor.template.insert_bidi_flag(BidiFlags::BOX_MIRRORING);
|
||||
},
|
||||
2501 => {
|
||||
trace!("Enabling paragraph direction auto-detection");
|
||||
self.grid.cursor.template.insert_bidi_flag(BidiFlags::AUTO_PARA_DIR);
|
||||
},
|
||||
_ => debug!("Ignoring unknown mode {} in set_private_mode", mode),
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "bidi_draft"))]
|
||||
debug!("Ignoring unknown mode {} in set_private_mode", mode);
|
||||
return;
|
||||
},
|
||||
|
@ -1964,6 +2047,26 @@ impl<T: EventListener> Handler for Term<T> {
|
|||
let mode = match mode {
|
||||
PrivateMode::Named(mode) => mode,
|
||||
PrivateMode::Unknown(mode) => {
|
||||
#[cfg(feature = "bidi_draft")]
|
||||
{
|
||||
use cell::BidiFlags;
|
||||
match mode {
|
||||
1243 => {
|
||||
trace!("Disabling RTL arrow key swapping");
|
||||
self.bidi_disable_arrow_key_swapping = true;
|
||||
},
|
||||
2500 => {
|
||||
trace!("Disabling RTL box mirroring");
|
||||
self.grid.cursor.template.remove_bidi_flag(BidiFlags::BOX_MIRRORING);
|
||||
},
|
||||
2501 => {
|
||||
trace!("Disabling paragraph direction auto-detection");
|
||||
self.grid.cursor.template.remove_bidi_flag(BidiFlags::AUTO_PARA_DIR);
|
||||
},
|
||||
_ => debug!("Ignoring unknown mode {} in unset_private_mode", mode),
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "bidi_draft"))]
|
||||
debug!("Ignoring unknown mode {} in unset_private_mode", mode);
|
||||
return;
|
||||
},
|
||||
|
@ -2065,6 +2168,15 @@ impl<T: EventListener> Handler for Term<T> {
|
|||
let mode = match mode {
|
||||
ansi::Mode::Named(mode) => mode,
|
||||
ansi::Mode::Unknown(mode) => {
|
||||
#[cfg(feature = "bidi_draft")]
|
||||
// BDSM
|
||||
if mode == 8 {
|
||||
trace!("Re-enabling BiDi implicit mode");
|
||||
self.grid.cursor.template.remove_bidi_flag(cell::BidiFlags::EXPLICIT_DIRECTION);
|
||||
} else {
|
||||
debug!("Ignoring unknown mode {} in set_mode", mode);
|
||||
}
|
||||
#[cfg(not(feature = "bidi_draft"))]
|
||||
debug!("Ignoring unknown mode {} in set_mode", mode);
|
||||
return;
|
||||
},
|
||||
|
@ -2082,6 +2194,15 @@ impl<T: EventListener> Handler for Term<T> {
|
|||
let mode = match mode {
|
||||
ansi::Mode::Named(mode) => mode,
|
||||
ansi::Mode::Unknown(mode) => {
|
||||
#[cfg(feature = "bidi_draft")]
|
||||
// BDSM
|
||||
if mode == 8 {
|
||||
trace!("Enabling BiDi explicit mode");
|
||||
self.grid.cursor.template.insert_bidi_flag(cell::BidiFlags::EXPLICIT_DIRECTION);
|
||||
} else {
|
||||
debug!("Ignorning unknown mode {} in unset_mode", mode);
|
||||
}
|
||||
#[cfg(not(feature = "bidi_draft"))]
|
||||
debug!("Ignorning unknown mode {} in unset_mode", mode);
|
||||
return;
|
||||
},
|
||||
|
@ -3271,3 +3392,275 @@ mod tests {
|
|||
assert_eq!(version_number("999.99.99"), 9_99_99_99);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "bidi_draft")]
|
||||
#[cfg(test)]
|
||||
mod bidi_tests {
|
||||
use super::*;
|
||||
use std::iter;
|
||||
|
||||
use crate::event::VoidListener;
|
||||
use crate::index::{Column as C, Line as L, Point as P};
|
||||
use crate::term::cell::{BidiDir, BidiMode};
|
||||
use crate::term::test::TermSize;
|
||||
|
||||
fn line_input(term: &mut Term<VoidListener>, s: impl Into<String>) {
|
||||
iter::repeat(s.into().chars()).flatten().take(term.columns()).for_each(|ch| term.input(ch));
|
||||
}
|
||||
|
||||
fn line_match(term: &Term<VoidListener>, l: L, s: impl Into<String>) {
|
||||
let s = s.into();
|
||||
let text = term.bounds_to_string(P::new(l, C(0)), P::new(l, C(term.columns())));
|
||||
assert_eq!(text, s.repeat(term.columns() / s.chars().count()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bidi_modes() {
|
||||
let size = TermSize::new(20, 5);
|
||||
let mut term = Term::new(Config::default(), &size, VoidListener);
|
||||
|
||||
// Starts in implicit default (non-auto)
|
||||
line_input(&mut term, 'D');
|
||||
|
||||
// Set auto-dir
|
||||
term.set_private_mode(PrivateMode::Unknown(2501));
|
||||
line_input(&mut term, 'A');
|
||||
|
||||
// Explicit overrides all
|
||||
term.unset_mode(ansi::Mode::Unknown(8));
|
||||
line_input(&mut term, 'E');
|
||||
|
||||
// Back to implicit auto
|
||||
term.set_mode(ansi::Mode::Unknown(8));
|
||||
line_input(&mut term, 'A');
|
||||
|
||||
// Unset auto
|
||||
term.unset_private_mode(PrivateMode::Unknown(2501));
|
||||
line_input(&mut term, 'D');
|
||||
|
||||
// ================================================== //
|
||||
// ===================== Output ===================== //
|
||||
// ================================================== //
|
||||
|
||||
// Default
|
||||
line_match(&term, L(0), 'D');
|
||||
assert_eq!(term.grid[L(0)][C(0)].bidi_mode(), BidiMode::Implicit {
|
||||
para_dir: BidiDir::Default
|
||||
});
|
||||
|
||||
// Set auto
|
||||
line_match(&term, L(1), 'A');
|
||||
assert_eq!(term.grid[L(1)][C(0)].bidi_mode(), BidiMode::Auto {
|
||||
fallback_para_dir: BidiDir::Default
|
||||
});
|
||||
|
||||
// Unset implicit => explicit
|
||||
line_match(&term, L(2), 'E');
|
||||
assert_eq!(term.grid[L(2)][C(0)].bidi_mode(), BidiMode::Explicit {
|
||||
forced_dir: BidiDir::Default
|
||||
});
|
||||
|
||||
// Set implicit => auto again
|
||||
line_match(&term, L(3), 'A');
|
||||
assert_eq!(term.grid[L(3)][C(0)].bidi_mode(), BidiMode::Auto {
|
||||
fallback_para_dir: BidiDir::Default
|
||||
});
|
||||
|
||||
// Unset auto => default again
|
||||
line_match(&term, L(4), 'D');
|
||||
assert_eq!(term.grid[L(4)][C(0)].bidi_mode(), BidiMode::Implicit {
|
||||
para_dir: BidiDir::Default
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bidi_dirs() {
|
||||
let size = TermSize::new(20, 18);
|
||||
let mut term = Term::new(Config::default(), &size, VoidListener);
|
||||
|
||||
// === Implicit (default) mode === //
|
||||
|
||||
// Starts in Default dir
|
||||
line_input(&mut term, "ID");
|
||||
|
||||
// Set LTR
|
||||
term.set_scp(ScpCharPath::LTR, ScpUpdateMode::ImplementationDependant);
|
||||
line_input(&mut term, "IL");
|
||||
|
||||
// Set RTL
|
||||
term.set_scp(ScpCharPath::RTL, ScpUpdateMode::ImplementationDependant);
|
||||
line_input(&mut term, "IR");
|
||||
|
||||
// Back To Default
|
||||
term.set_scp(ScpCharPath::Default, ScpUpdateMode::ImplementationDependant);
|
||||
line_input(&mut term, "ID");
|
||||
|
||||
// Unhandled update modes (no effect)
|
||||
term.set_scp(ScpCharPath::RTL, ScpUpdateMode::DataToPresentation);
|
||||
line_input(&mut term, "ID");
|
||||
term.set_scp(ScpCharPath::RTL, ScpUpdateMode::PresentationToData);
|
||||
line_input(&mut term, "ID");
|
||||
|
||||
// === Implicit auto-dir === //
|
||||
term.set_private_mode(PrivateMode::Unknown(2501));
|
||||
|
||||
// Starts in Default dir
|
||||
line_input(&mut term, "AD");
|
||||
|
||||
// Set LTR
|
||||
term.set_scp(ScpCharPath::LTR, ScpUpdateMode::ImplementationDependant);
|
||||
line_input(&mut term, "AL");
|
||||
|
||||
// Set RTL
|
||||
term.set_scp(ScpCharPath::RTL, ScpUpdateMode::ImplementationDependant);
|
||||
line_input(&mut term, "AR");
|
||||
|
||||
// Back To Default
|
||||
term.set_scp(ScpCharPath::Default, ScpUpdateMode::ImplementationDependant);
|
||||
line_input(&mut term, "AD");
|
||||
|
||||
// Unhandled update modes (no effect)
|
||||
term.set_scp(ScpCharPath::RTL, ScpUpdateMode::DataToPresentation);
|
||||
line_input(&mut term, "AD");
|
||||
term.set_scp(ScpCharPath::RTL, ScpUpdateMode::PresentationToData);
|
||||
line_input(&mut term, "AD");
|
||||
|
||||
// === Explicit === //
|
||||
term.unset_mode(ansi::Mode::Unknown(8));
|
||||
|
||||
// Starts in Default dir
|
||||
line_input(&mut term, "ED");
|
||||
|
||||
// Set LTR
|
||||
term.set_scp(ScpCharPath::LTR, ScpUpdateMode::ImplementationDependant);
|
||||
line_input(&mut term, "EL");
|
||||
|
||||
// Set RTL
|
||||
term.set_scp(ScpCharPath::RTL, ScpUpdateMode::ImplementationDependant);
|
||||
line_input(&mut term, "ER");
|
||||
|
||||
// Back To Default
|
||||
term.set_scp(ScpCharPath::Default, ScpUpdateMode::ImplementationDependant);
|
||||
line_input(&mut term, "ED");
|
||||
|
||||
// Unhandled update modes (no effect)
|
||||
term.set_scp(ScpCharPath::RTL, ScpUpdateMode::DataToPresentation);
|
||||
line_input(&mut term, "ED");
|
||||
term.set_scp(ScpCharPath::RTL, ScpUpdateMode::PresentationToData);
|
||||
line_input(&mut term, "ED");
|
||||
|
||||
// ================================================== //
|
||||
// ===================== Output ===================== //
|
||||
// ================================================== //
|
||||
|
||||
// === Implicit (default) mode === //
|
||||
|
||||
// Default
|
||||
line_match(&term, L(0), "ID");
|
||||
assert_eq!(term.grid[L(0)][C(0)].bidi_mode(), BidiMode::Implicit {
|
||||
para_dir: BidiDir::Default
|
||||
});
|
||||
|
||||
// LTR
|
||||
line_match(&term, L(1), "IL");
|
||||
assert_eq!(term.grid[L(1)][C(0)].bidi_mode(), BidiMode::Implicit {
|
||||
para_dir: BidiDir::LTR
|
||||
});
|
||||
|
||||
// RTL
|
||||
line_match(&term, L(2), "IR");
|
||||
assert_eq!(term.grid[L(2)][C(0)].bidi_mode(), BidiMode::Implicit {
|
||||
para_dir: BidiDir::RTL
|
||||
});
|
||||
|
||||
// Back To Default
|
||||
line_match(&term, L(3), "ID");
|
||||
assert_eq!(term.grid[L(3)][C(0)].bidi_mode(), BidiMode::Implicit {
|
||||
para_dir: BidiDir::Default
|
||||
});
|
||||
|
||||
// Unhandled update modes (no effect)
|
||||
line_match(&term, L(4), "ID");
|
||||
assert_eq!(term.grid[L(4)][C(0)].bidi_mode(), BidiMode::Implicit {
|
||||
para_dir: BidiDir::Default
|
||||
});
|
||||
line_match(&term, L(5), "ID");
|
||||
assert_eq!(term.grid[L(5)][C(0)].bidi_mode(), BidiMode::Implicit {
|
||||
para_dir: BidiDir::Default
|
||||
});
|
||||
|
||||
// === Implicit auto-dir === //
|
||||
|
||||
// Default
|
||||
line_match(&term, L(6), "AD");
|
||||
assert_eq!(term.grid[L(6)][C(0)].bidi_mode(), BidiMode::Auto {
|
||||
fallback_para_dir: BidiDir::Default
|
||||
});
|
||||
|
||||
// LTR
|
||||
line_match(&term, L(7), "AL");
|
||||
assert_eq!(term.grid[L(7)][C(0)].bidi_mode(), BidiMode::Auto {
|
||||
fallback_para_dir: BidiDir::LTR
|
||||
});
|
||||
|
||||
// RTL
|
||||
line_match(&term, L(8), "AR");
|
||||
assert_eq!(term.grid[L(8)][C(0)].bidi_mode(), BidiMode::Auto {
|
||||
fallback_para_dir: BidiDir::RTL
|
||||
});
|
||||
|
||||
// Back To Default
|
||||
line_match(&term, L(9), "AD");
|
||||
assert_eq!(term.grid[L(9)][C(0)].bidi_mode(), BidiMode::Auto {
|
||||
fallback_para_dir: BidiDir::Default
|
||||
});
|
||||
|
||||
// Unhandled update modes (no effect)
|
||||
line_match(&term, L(10), "AD");
|
||||
assert_eq!(term.grid[L(10)][C(0)].bidi_mode(), BidiMode::Auto {
|
||||
fallback_para_dir: BidiDir::Default
|
||||
});
|
||||
line_match(&term, L(11), "AD");
|
||||
assert_eq!(term.grid[L(11)][C(0)].bidi_mode(), BidiMode::Auto {
|
||||
fallback_para_dir: BidiDir::Default
|
||||
});
|
||||
|
||||
// === Explicit === //
|
||||
|
||||
// Default
|
||||
line_match(&term, L(12), "ED");
|
||||
assert_eq!(term.grid[L(12)][C(0)].bidi_mode(), BidiMode::Explicit {
|
||||
forced_dir: BidiDir::Default
|
||||
});
|
||||
|
||||
// LTR
|
||||
line_match(&term, L(13), "EL");
|
||||
assert_eq!(term.grid[L(13)][C(0)].bidi_mode(), BidiMode::Explicit {
|
||||
forced_dir: BidiDir::LTR
|
||||
});
|
||||
|
||||
// RTL
|
||||
line_match(&term, L(14), "ER");
|
||||
assert_eq!(term.grid[L(14)][C(0)].bidi_mode(), BidiMode::Explicit {
|
||||
forced_dir: BidiDir::RTL
|
||||
});
|
||||
|
||||
// Back To Default
|
||||
line_match(&term, L(15), "ED");
|
||||
assert_eq!(term.grid[L(15)][C(0)].bidi_mode(), BidiMode::Explicit {
|
||||
forced_dir: BidiDir::Default
|
||||
});
|
||||
|
||||
// Unhandled update modes (no effect)
|
||||
line_match(&term, L(16), "ED");
|
||||
assert_eq!(term.grid[L(16)][C(0)].bidi_mode(), BidiMode::Explicit {
|
||||
forced_dir: BidiDir::Default
|
||||
});
|
||||
line_match(&term, L(17), "ED");
|
||||
assert_eq!(term.grid[L(17)][C(0)].bidi_mode(), BidiMode::Explicit {
|
||||
forced_dir: BidiDir::Default
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: box mirroring, arrow swapping
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue