mirror of
https://github.com/alacritty/alacritty.git
synced 2024-11-03 04:34:21 -05:00
ansi: Designate and invoke graphic character sets
Implement the designation of graphic character sets G0-G3 to ASCII or the Special character and line drawing glyphs. As well as the invokation/selection of the character sets (shift in, shift out and lock shifting).
This commit is contained in:
parent
6d0abe2607
commit
6229477190
2 changed files with 236 additions and 8 deletions
122
src/ansi.rs
122
src/ansi.rs
|
@ -238,6 +238,18 @@ pub trait Handler {
|
|||
|
||||
/// DECKPNM - Set keypad to numeric mode (digits intead of ESCape seq)
|
||||
fn unset_keypad_application_mode(&mut self) {}
|
||||
|
||||
/// Set one of the graphic character sets, G0 to G3, as the active charset.
|
||||
///
|
||||
/// 'Invoke' one of G0 to G3 in the GL area. Also refered to as shift in,
|
||||
/// shift out and locking shift depending on the set being activated
|
||||
fn set_active_charset(&mut self, CharsetIndex) {}
|
||||
|
||||
/// Assign a graphic character set to G0, G1, G2 or G3
|
||||
///
|
||||
/// 'Designate' a graphic character set as one of G0 to G3, so that it can
|
||||
/// later be 'invoked' by `set_active_charset`
|
||||
fn configure_charset(&mut self, CharsetIndex, StandardCharset) {}
|
||||
}
|
||||
|
||||
/// Terminal modes
|
||||
|
@ -436,6 +448,23 @@ pub enum Attr {
|
|||
Background(Color),
|
||||
}
|
||||
|
||||
/// Identifiers which can be assigned to a graphic character set
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum CharsetIndex {
|
||||
/// Default set, is designated as ASCII at startup
|
||||
G0,
|
||||
G1,
|
||||
G2,
|
||||
G3,
|
||||
}
|
||||
|
||||
/// Standard or common character sets which can be designated as G0-G3
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum StandardCharset {
|
||||
Ascii,
|
||||
SpecialCharacterAndLineDrawing,
|
||||
}
|
||||
|
||||
impl<'a, H, W> vte::Perform for Performer<'a, H, W>
|
||||
where H: Handler + TermInfo + 'a,
|
||||
W: io::Write + 'a
|
||||
|
@ -454,6 +483,8 @@ impl<'a, H, W> vte::Perform for Performer<'a, H, W>
|
|||
C0::LF | C0::VT | C0::FF => self.handler.linefeed(),
|
||||
C0::BEL => self.handler.bell(),
|
||||
C0::SUB => self.handler.substitute(),
|
||||
C0::SI => self.handler.set_active_charset(CharsetIndex::G0),
|
||||
C0::SO => self.handler.set_active_charset(CharsetIndex::G1),
|
||||
C1::NEL => self.handler.newline(),
|
||||
C1::HTS => self.handler.set_horizontal_tabstop(),
|
||||
C1::DECID => self.handler.identify_terminal(self.writer),
|
||||
|
@ -742,19 +773,41 @@ impl<'a, H, W> vte::Perform for Performer<'a, H, W>
|
|||
_ignore: bool,
|
||||
byte: u8
|
||||
) {
|
||||
macro_rules! unhandled {
|
||||
() => {{
|
||||
err_println!("[unhandled] esc_dispatch params={:?}, ints={:?}, byte={:?} ({:02x})",
|
||||
params, intermediates, byte as char, byte);
|
||||
return;
|
||||
}}
|
||||
}
|
||||
|
||||
macro_rules! configure_charset {
|
||||
($charset:path) => {{
|
||||
let index: CharsetIndex = match intermediates.first().cloned() {
|
||||
Some(b'(') => CharsetIndex::G0,
|
||||
Some(b')') => CharsetIndex::G1,
|
||||
Some(b'*') => CharsetIndex::G2,
|
||||
Some(b'+') => CharsetIndex::G3,
|
||||
_ => unhandled!(),
|
||||
};
|
||||
self.handler.configure_charset(index, $charset)
|
||||
}}
|
||||
}
|
||||
|
||||
match byte {
|
||||
b'B' => configure_charset!(StandardCharset::Ascii),
|
||||
b'D' => self.handler.linefeed(),
|
||||
b'E' => self.handler.newline(),
|
||||
b'H' => self.handler.set_horizontal_tabstop(),
|
||||
b'M' => self.handler.reverse_index(),
|
||||
b'Z' => self.handler.identify_terminal(self.writer),
|
||||
b'c' => self.handler.reset_state(),
|
||||
b'0' => configure_charset!(StandardCharset::SpecialCharacterAndLineDrawing),
|
||||
b'7' => self.handler.save_cursor_position(),
|
||||
b'8' => self.handler.restore_cursor_position(),
|
||||
b'=' => self.handler.set_keypad_application_mode(),
|
||||
b'>' => self.handler.unset_keypad_application_mode(),
|
||||
_ => err_println!("[unhandled] esc_dispatch params={:?}, ints={:?}, byte={:?} ({:02x})",
|
||||
params, intermediates, byte as char, byte),
|
||||
_ => unhandled!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -969,7 +1022,7 @@ pub mod C1 {
|
|||
mod tests {
|
||||
use std::io;
|
||||
use index::{Line, Column};
|
||||
use super::{Processor, Handler, Attr, TermInfo, Color};
|
||||
use super::{Processor, Handler, Attr, TermInfo, Color, StandardCharset, CharsetIndex};
|
||||
use ::Rgb;
|
||||
|
||||
/// The /dev/null of io::Write
|
||||
|
@ -1074,4 +1127,67 @@ mod tests {
|
|||
parser.advance(&mut handler, *byte, &mut Void);
|
||||
}
|
||||
}
|
||||
|
||||
struct CharsetHandler {
|
||||
index: CharsetIndex,
|
||||
charset: StandardCharset,
|
||||
}
|
||||
|
||||
impl Default for CharsetHandler {
|
||||
fn default() -> CharsetHandler {
|
||||
CharsetHandler {
|
||||
index: CharsetIndex::G0,
|
||||
charset: StandardCharset::Ascii,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Handler for CharsetHandler {
|
||||
fn configure_charset(&mut self, index: CharsetIndex, charset: StandardCharset) {
|
||||
self.index = index;
|
||||
self.charset = charset;
|
||||
}
|
||||
|
||||
fn set_active_charset(&mut self, index: CharsetIndex) {
|
||||
self.index = index;
|
||||
}
|
||||
}
|
||||
|
||||
impl TermInfo for CharsetHandler {
|
||||
fn lines(&self) -> Line { Line(200) }
|
||||
fn cols(&self) -> Column { Column(90) }
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_designate_g0_as_line_drawing() {
|
||||
static BYTES: &'static [u8] = &[0x1b, b'(', b'0'];
|
||||
let mut parser = Processor::new();
|
||||
let mut handler = CharsetHandler::default();
|
||||
|
||||
for byte in &BYTES[..] {
|
||||
parser.advance(&mut handler, *byte, &mut Void);
|
||||
}
|
||||
|
||||
assert_eq!(handler.index, CharsetIndex::G0);
|
||||
assert_eq!(handler.charset, StandardCharset::SpecialCharacterAndLineDrawing);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_designate_g1_as_line_drawing_and_invoke() {
|
||||
static BYTES: &'static [u8] = &[0x1b, 0x29, 0x30, 0x0e];
|
||||
let mut parser = Processor::new();
|
||||
let mut handler = CharsetHandler::default();
|
||||
|
||||
for byte in &BYTES[..3] {
|
||||
parser.advance(&mut handler, *byte, &mut Void);
|
||||
}
|
||||
|
||||
assert_eq!(handler.index, CharsetIndex::G1);
|
||||
assert_eq!(handler.charset, StandardCharset::SpecialCharacterAndLineDrawing);
|
||||
|
||||
let mut handler = CharsetHandler::default();
|
||||
parser.advance(&mut handler, BYTES[3], &mut Void);
|
||||
|
||||
assert_eq!(handler.index, CharsetIndex::G1);
|
||||
}
|
||||
}
|
||||
|
|
122
src/term/mod.rs
122
src/term/mod.rs
|
@ -13,12 +13,12 @@
|
|||
// limitations under the License.
|
||||
//
|
||||
//! Exports the `Term` type which is a high-level API for the Grid
|
||||
use std::ops::{Deref, Range};
|
||||
use std::ops::{Deref, Range, Index, IndexMut};
|
||||
use std::ptr;
|
||||
use std::cmp::min;
|
||||
use std::io;
|
||||
|
||||
use ansi::{self, Color, NamedColor, Attr, Handler};
|
||||
use ansi::{self, Color, NamedColor, Attr, Handler, CharsetIndex, StandardCharset};
|
||||
use grid::{Grid, ClearRegion, ToRange};
|
||||
use index::{self, Point, Column, Line, Linear, IndexRange, Contains, RangeInclusive};
|
||||
use selection::{Span, Selection};
|
||||
|
@ -200,6 +200,79 @@ pub use self::mode::TermMode;
|
|||
|
||||
pub const TAB_SPACES: usize = 8;
|
||||
|
||||
trait CharsetMapping {
|
||||
fn map(&self, c: char) -> char {
|
||||
c
|
||||
}
|
||||
}
|
||||
|
||||
impl CharsetMapping for StandardCharset {
|
||||
/// Switch/Map character to the active charset. Ascii is the common case and
|
||||
/// for that we want to do as little as possible.
|
||||
#[inline]
|
||||
fn map(&self, c: char) -> char {
|
||||
match *self {
|
||||
StandardCharset::Ascii => c,
|
||||
StandardCharset::SpecialCharacterAndLineDrawing =>
|
||||
match c {
|
||||
'`' => '◆',
|
||||
'a' => '▒',
|
||||
'b' => '\t',
|
||||
'c' => '\u{000c}',
|
||||
'd' => '\r',
|
||||
'e' => '\n',
|
||||
'f' => '°',
|
||||
'g' => '±',
|
||||
'h' => '\u{2424}',
|
||||
'i' => '\u{000b}',
|
||||
'j' => '┘',
|
||||
'k' => '┐',
|
||||
'l' => '┌',
|
||||
'm' => '└',
|
||||
'n' => '┼',
|
||||
'o' => '⎺',
|
||||
'p' => '⎻',
|
||||
'q' => '─',
|
||||
'r' => '⎼',
|
||||
's' => '⎽',
|
||||
't' => '├',
|
||||
'u' => '┤',
|
||||
'v' => '┴',
|
||||
'w' => '┬',
|
||||
'x' => '│',
|
||||
'y' => '≤',
|
||||
'z' => '≥',
|
||||
'{' => 'π',
|
||||
'|' => '≠',
|
||||
'}' => '£',
|
||||
'~' => '·',
|
||||
_ => c
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Charsets([StandardCharset; 4]);
|
||||
|
||||
impl Charsets {
|
||||
fn new() -> Charsets {
|
||||
Charsets([StandardCharset::Ascii; 4])
|
||||
}
|
||||
}
|
||||
|
||||
impl Index<CharsetIndex> for Charsets {
|
||||
type Output = StandardCharset;
|
||||
fn index(&self, index: CharsetIndex) -> &StandardCharset {
|
||||
&self.0[index as usize]
|
||||
}
|
||||
}
|
||||
|
||||
impl IndexMut<CharsetIndex> for Charsets {
|
||||
fn index_mut(&mut self, index: CharsetIndex) -> &mut StandardCharset {
|
||||
&mut self.0[index as usize]
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Term {
|
||||
/// The grid
|
||||
grid: Grid<Cell>,
|
||||
|
@ -228,6 +301,13 @@ pub struct Term {
|
|||
/// Alt cursor
|
||||
alt_cursor: Point,
|
||||
|
||||
/// Currently configured graphic character sets
|
||||
charsets: Charsets,
|
||||
|
||||
/// The graphic character set, out of `charsets`, which ASCII is currently
|
||||
/// being mapped to
|
||||
active_charset: CharsetIndex,
|
||||
|
||||
/// Tabstops
|
||||
tabs: Vec<bool>,
|
||||
|
||||
|
@ -323,6 +403,8 @@ impl Term {
|
|||
alt: false,
|
||||
cursor: Point::default(),
|
||||
alt_cursor: Point::default(),
|
||||
active_charset: CharsetIndex::G0,
|
||||
charsets: Charsets::new(),
|
||||
tabs: tabs,
|
||||
mode: Default::default(),
|
||||
scroll_region: scroll_region,
|
||||
|
@ -695,7 +777,7 @@ impl ansi::Handler for Term {
|
|||
{
|
||||
let cell = &mut self.grid[&self.cursor];
|
||||
*cell = self.template_cell;
|
||||
cell.c = c;
|
||||
cell.c = self.charsets[self.active_charset].map(c);
|
||||
}
|
||||
|
||||
if (self.cursor.col + 1) < self.grid.num_cols() {
|
||||
|
@ -1109,6 +1191,18 @@ impl ansi::Handler for Term {
|
|||
debug_println!("unset mode::APP_KEYPAD");
|
||||
self.mode.remove(mode::APP_KEYPAD);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn configure_charset(&mut self, index: CharsetIndex, charset: StandardCharset) {
|
||||
debug_println!("designate {:?} character set as {:?}", index, charset);
|
||||
self.charsets[index] = charset;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_active_charset(&mut self, index: CharsetIndex) {
|
||||
debug_println!("Activate {:?} character set", index);
|
||||
self.active_charset = index;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -1116,11 +1210,12 @@ mod tests {
|
|||
extern crate serde_json;
|
||||
extern crate test;
|
||||
|
||||
use super::limit;
|
||||
use super::{Term, limit, SizeInfo};
|
||||
|
||||
use grid::Grid;
|
||||
use index::{Line, Column};
|
||||
use index::{Point, Line, Column};
|
||||
use term::{Cell};
|
||||
use ansi::{Handler, CharsetIndex, StandardCharset};
|
||||
|
||||
/// Check that the grid can be serialized back and forth losslessly
|
||||
///
|
||||
|
@ -1144,6 +1239,23 @@ mod tests {
|
|||
assert_eq!(limit(5, 6, 10), 6);
|
||||
assert_eq!(limit(5, 1, 4), 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn input_line_drawing_character() {
|
||||
let size = SizeInfo {
|
||||
width: 21.0,
|
||||
height: 51.0,
|
||||
cell_width: 3.0,
|
||||
cell_height: 3.0,
|
||||
};
|
||||
let mut term = Term::new(size);
|
||||
let cursor = Point::new(Line(0), Column(0));
|
||||
term.configure_charset(CharsetIndex::G0,
|
||||
StandardCharset::SpecialCharacterAndLineDrawing);
|
||||
term.input('a');
|
||||
|
||||
assert_eq!(term.grid()[&cursor].c, '▒');
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
Loading…
Reference in a new issue