Convert theming to Rust.
This commit is contained in:
parent
6378f6ee6b
commit
eef1d2041d
5 changed files with 221 additions and 76 deletions
|
@ -8,6 +8,11 @@ authors = [
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
cpython = "0.7"
|
cpython = "0.7"
|
||||||
|
nom = "4"
|
||||||
|
chrono = "0.4"
|
||||||
|
ncurses = "5"
|
||||||
|
lazy_static = "1"
|
||||||
|
enum-set = "0.0"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["cdylib"]
|
crate-type = ["cdylib"]
|
||||||
|
|
2
poezio/libpoezio.pyi
Normal file
2
poezio/libpoezio.pyi
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
|
||||||
|
def to_curses_attr(fg: int, bg: int, attrs: str) -> int: ...
|
|
@ -76,8 +76,8 @@ import functools
|
||||||
from typing import Dict, List, Union, Tuple, Optional, cast
|
from typing import Dict, List, Union, Tuple, Optional, cast
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from os import path
|
from os import path
|
||||||
from poezio import colors, xdg
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from poezio import colors, xdg, libpoezio
|
||||||
|
|
||||||
from importlib import machinery
|
from importlib import machinery
|
||||||
finder = machinery.PathFinder()
|
finder = machinery.PathFinder()
|
||||||
|
@ -399,43 +399,9 @@ class Theme:
|
||||||
# This is the default theme object, used if no theme is defined in the conf
|
# This is the default theme object, used if no theme is defined in the conf
|
||||||
theme = Theme()
|
theme = Theme()
|
||||||
|
|
||||||
# a dict "color tuple -> color_pair"
|
|
||||||
# Each time we use a color tuple, we check if it has already been used.
|
|
||||||
# If not we create a new color_pair and keep it in that dict, to use it
|
|
||||||
# the next time.
|
|
||||||
curses_colors_dict: Dict[Union[Tuple[int, int], Tuple[int, int, str]], int] = {}
|
|
||||||
|
|
||||||
# yapf: disable
|
|
||||||
|
|
||||||
table_256_to_16 = [
|
|
||||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
|
|
||||||
0, 4, 4, 4, 12, 12, 2, 6, 4, 4, 12, 12, 2, 2, 6, 4,
|
|
||||||
12, 12, 2, 2, 2, 6, 12, 12, 10, 10, 10, 10, 14, 12, 10, 10,
|
|
||||||
10, 10, 10, 14, 1, 5, 4, 4, 12, 12, 3, 8, 4, 4, 12, 12,
|
|
||||||
2, 2, 6, 4, 12, 12, 2, 2, 2, 6, 12, 12, 10, 10, 10, 10,
|
|
||||||
14, 12, 10, 10, 10, 10, 10, 14, 1, 1, 5, 4, 12, 12, 1, 1,
|
|
||||||
5, 4, 12, 12, 3, 3, 8, 4, 12, 12, 2, 2, 2, 6, 12, 12,
|
|
||||||
10, 10, 10, 10, 14, 12, 10, 10, 10, 10, 10, 14, 1, 1, 1, 5,
|
|
||||||
12, 12, 1, 1, 1, 5, 12, 12, 1, 1, 1, 5, 12, 12, 3, 3,
|
|
||||||
3, 7, 12, 12, 10, 10, 10, 10, 14, 12, 10, 10, 10, 10, 10, 14,
|
|
||||||
9, 9, 9, 9, 13, 12, 9, 9, 9, 9, 13, 12, 9, 9, 9, 9,
|
|
||||||
13, 12, 9, 9, 9, 9, 13, 12, 11, 11, 11, 11, 7, 12, 10, 10,
|
|
||||||
10, 10, 10, 14, 9, 9, 9, 9, 9, 13, 9, 9, 9, 9, 9, 13,
|
|
||||||
9, 9, 9, 9, 9, 13, 9, 9, 9, 9, 9, 13, 9, 9, 9, 9,
|
|
||||||
9, 13, 11, 11, 11, 11, 11, 15, 0, 0, 0, 0, 0, 0, 8, 8,
|
|
||||||
8, 8, 8, 8, 7, 7, 7, 7, 7, 7, 15, 15, 15, 15, 15, 15
|
|
||||||
]
|
|
||||||
# yapf: enable
|
|
||||||
|
|
||||||
load_path: List[str] = []
|
load_path: List[str] = []
|
||||||
|
|
||||||
|
|
||||||
def color_256_to_16(color):
|
|
||||||
if color == -1:
|
|
||||||
return color
|
|
||||||
return table_256_to_16[color]
|
|
||||||
|
|
||||||
|
|
||||||
def dump_tuple(tup: Union[Tuple[int, int], Tuple[int, int, str]]) -> str:
|
def dump_tuple(tup: Union[Tuple[int, int], Tuple[int, int, str]]) -> str:
|
||||||
"""
|
"""
|
||||||
Dump a tuple to a string of fg,bg,attr (optional)
|
Dump a tuple to a string of fg,bg,attr (optional)
|
||||||
|
@ -454,52 +420,14 @@ def read_tuple(_str: str) -> Tuple[Tuple[int, int], str]:
|
||||||
|
|
||||||
@functools.lru_cache(maxsize=128)
|
@functools.lru_cache(maxsize=128)
|
||||||
def to_curses_attr(
|
def to_curses_attr(
|
||||||
color_tuple: Union[Tuple[int, int], Tuple[int, int, str]]) -> int:
|
ccolors: Union[Tuple[int, int], Tuple[int, int, str]]) -> int:
|
||||||
"""
|
"""
|
||||||
Takes a color tuple (as defined at the top of this file) and
|
Takes a color tuple (as defined at the top of this file) and
|
||||||
returns a valid curses attr that can be passed directly to attron() or attroff()
|
returns a valid curses attr that can be passed directly to attron() or attroff()
|
||||||
"""
|
"""
|
||||||
# extract the color from that tuple
|
# extract the color from that tuple
|
||||||
colors: Union[Tuple[int, int], Tuple[int, int, str]]
|
attrs = '' if len(ccolors) < 3 else ccolors[2] # type: ignore
|
||||||
if len(color_tuple) == 3:
|
return libpoezio.to_curses_attr(ccolors[0], ccolors[1], attrs)
|
||||||
colors = (color_tuple[0], color_tuple[1])
|
|
||||||
else:
|
|
||||||
colors = color_tuple
|
|
||||||
|
|
||||||
bold = False
|
|
||||||
if curses.COLORS < 256:
|
|
||||||
# We are not in a term supporting 256 colors, so we convert
|
|
||||||
# colors to numbers between -1 and 8
|
|
||||||
colors = (color_256_to_16(colors[0]), color_256_to_16(colors[1]))
|
|
||||||
if colors[0] >= 8:
|
|
||||||
colors = (colors[0] - 8, colors[1])
|
|
||||||
bold = True
|
|
||||||
if colors[1] >= 8:
|
|
||||||
colors = (colors[0], colors[1] - 8)
|
|
||||||
|
|
||||||
# check if we already used these colors
|
|
||||||
try:
|
|
||||||
pair = curses_colors_dict[colors]
|
|
||||||
except KeyError:
|
|
||||||
pair = len(curses_colors_dict) + 1
|
|
||||||
curses.init_pair(pair, colors[0], colors[1])
|
|
||||||
curses_colors_dict[colors] = pair
|
|
||||||
curses_pair = curses.color_pair(pair)
|
|
||||||
if len(color_tuple) == 3:
|
|
||||||
_, _, additional_val = cast(Tuple[int, int, str], color_tuple)
|
|
||||||
if 'b' in additional_val or bold is True:
|
|
||||||
curses_pair = curses_pair | curses.A_BOLD
|
|
||||||
if 'u' in additional_val:
|
|
||||||
curses_pair = curses_pair | curses.A_UNDERLINE
|
|
||||||
if 'i' in additional_val:
|
|
||||||
curses_pair = curses_pair | (curses.A_ITALIC if hasattr(
|
|
||||||
curses, 'A_ITALIC') else curses.A_REVERSE)
|
|
||||||
if 'a' in additional_val:
|
|
||||||
curses_pair = curses_pair | curses.A_BLINK
|
|
||||||
if 'r' in additional_val:
|
|
||||||
curses_pair = curses_pair | curses.A_REVERSE
|
|
||||||
return curses_pair
|
|
||||||
|
|
||||||
|
|
||||||
def get_theme() -> Theme:
|
def get_theme() -> Theme:
|
||||||
"""
|
"""
|
||||||
|
|
49
src/lib.rs
49
src/lib.rs
|
@ -1,6 +1,55 @@
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate cpython;
|
extern crate cpython;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate nom;
|
||||||
|
extern crate ncurses;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate lazy_static;
|
||||||
|
extern crate enum_set;
|
||||||
|
|
||||||
|
pub mod theming;
|
||||||
|
|
||||||
|
use self::theming::{curses_attr, parse_attrs};
|
||||||
|
use cpython::{PyErr, PyObject, PyResult, Python, PythonObject, ToPyObject};
|
||||||
|
|
||||||
py_module_initializer!(libpoezio, initlibpoezio, PyInit_libpoezio, |py, m| {
|
py_module_initializer!(libpoezio, initlibpoezio, PyInit_libpoezio, |py, m| {
|
||||||
|
m.add(
|
||||||
|
py,
|
||||||
|
"to_curses_attr",
|
||||||
|
py_fn!(py, to_curses_attr(fg: i16, bg: i16, attrs: &str)),
|
||||||
|
)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
});
|
});
|
||||||
|
|
||||||
|
py_exception!(libpoezio, LogParseError);
|
||||||
|
|
||||||
|
macro_rules! py_int {
|
||||||
|
($py:ident, $i:expr) => {
|
||||||
|
$i.to_py_object($py).into_object()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nom_to_py_err(py: Python, err: nom::Err<&str>) -> PyErr {
|
||||||
|
PyErr {
|
||||||
|
ptype: py.get_type::<LogParseError>().into_object(),
|
||||||
|
pvalue: Some(
|
||||||
|
LogParseError(
|
||||||
|
err.into_error_kind()
|
||||||
|
.description()
|
||||||
|
.to_py_object(py)
|
||||||
|
.into_object(),
|
||||||
|
)
|
||||||
|
.into_object(),
|
||||||
|
),
|
||||||
|
ptraceback: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_curses_attr(py: Python, fg: i16, bg: i16, attrs: &str) -> PyResult<PyObject> {
|
||||||
|
let attrs = match parse_attrs(attrs) {
|
||||||
|
Ok(attrs) => attrs.1,
|
||||||
|
Err(err) => return Err(nom_to_py_err(py, err)),
|
||||||
|
};
|
||||||
|
let result = curses_attr(fg, bg, attrs);
|
||||||
|
Ok(py_int!(py, result))
|
||||||
|
}
|
||||||
|
|
161
src/theming.rs
Normal file
161
src/theming.rs
Normal file
|
@ -0,0 +1,161 @@
|
||||||
|
use enum_set::{CLike, EnumSet};
|
||||||
|
use ncurses::{attr_t, init_pair, A_BLINK, A_BOLD, A_ITALIC, A_UNDERLINE, COLORS, COLOR_PAIR};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::mem;
|
||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||||
|
#[repr(u32)]
|
||||||
|
pub enum Attr {
|
||||||
|
Bold,
|
||||||
|
Italic,
|
||||||
|
Underline,
|
||||||
|
Blink,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Attr {
|
||||||
|
pub fn get_attron(&self) -> attr_t {
|
||||||
|
match *self {
|
||||||
|
Attr::Bold => A_BOLD(),
|
||||||
|
Attr::Italic => A_ITALIC(),
|
||||||
|
Attr::Underline => A_UNDERLINE(),
|
||||||
|
Attr::Blink => A_BLINK(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CLike for Attr {
|
||||||
|
fn to_u32(&self) -> u32 {
|
||||||
|
*self as u32
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn from_u32(v: u32) -> Self {
|
||||||
|
mem::transmute(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
named!(
|
||||||
|
pub(crate) parse_attrs<&str, EnumSet<Attr>>,
|
||||||
|
do_parse!(
|
||||||
|
vec: many0!(alt_complete!(
|
||||||
|
tag!("b") => { |_| Attr::Bold } |
|
||||||
|
tag!("i") => { |_| Attr::Italic } |
|
||||||
|
tag!("u") => { |_| Attr::Underline } |
|
||||||
|
tag!("a") => { |_| Attr::Blink }
|
||||||
|
)) >>
|
||||||
|
({
|
||||||
|
let mut set = EnumSet::new();
|
||||||
|
set.extend(vec);
|
||||||
|
set
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
// TODO: probably replace that mutex with an atomic.
|
||||||
|
static ref NEXT_PAIR: Mutex<i16> = Mutex::new(1);
|
||||||
|
|
||||||
|
/// a dict "color tuple -> color_pair"
|
||||||
|
/// Each time we use a color tuple, we check if it has already been used.
|
||||||
|
/// If not we create a new color_pair and keep it in that dict, to use it
|
||||||
|
/// the next time.
|
||||||
|
static ref COLOURS_DICT: Mutex<HashMap<(i16, i16), i16>> = {
|
||||||
|
Mutex::new(HashMap::new())
|
||||||
|
};
|
||||||
|
|
||||||
|
static ref TABLE_256_TO_16: Vec<u8> = vec![
|
||||||
|
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
|
||||||
|
0, 4, 4, 4, 12, 12, 2, 6, 4, 4, 12, 12, 2, 2, 6, 4,
|
||||||
|
12, 12, 2, 2, 2, 6, 12, 12, 10, 10, 10, 10, 14, 12, 10, 10,
|
||||||
|
10, 10, 10, 14, 1, 5, 4, 4, 12, 12, 3, 8, 4, 4, 12, 12,
|
||||||
|
2, 2, 6, 4, 12, 12, 2, 2, 2, 6, 12, 12, 10, 10, 10, 10,
|
||||||
|
14, 12, 10, 10, 10, 10, 10, 14, 1, 1, 5, 4, 12, 12, 1, 1,
|
||||||
|
5, 4, 12, 12, 3, 3, 8, 4, 12, 12, 2, 2, 2, 6, 12, 12,
|
||||||
|
10, 10, 10, 10, 14, 12, 10, 10, 10, 10, 10, 14, 1, 1, 1, 5,
|
||||||
|
12, 12, 1, 1, 1, 5, 12, 12, 1, 1, 1, 5, 12, 12, 3, 3,
|
||||||
|
3, 7, 12, 12, 10, 10, 10, 10, 14, 12, 10, 10, 10, 10, 10, 14,
|
||||||
|
9, 9, 9, 9, 13, 12, 9, 9, 9, 9, 13, 12, 9, 9, 9, 9,
|
||||||
|
13, 12, 9, 9, 9, 9, 13, 12, 11, 11, 11, 11, 7, 12, 10, 10,
|
||||||
|
10, 10, 10, 14, 9, 9, 9, 9, 9, 13, 9, 9, 9, 9, 9, 13,
|
||||||
|
9, 9, 9, 9, 9, 13, 9, 9, 9, 9, 9, 13, 9, 9, 9, 9,
|
||||||
|
9, 13, 11, 11, 11, 11, 11, 15, 0, 0, 0, 0, 0, 0, 8, 8,
|
||||||
|
8, 8, 8, 8, 7, 7, 7, 7, 7, 7, 15, 15, 15, 15, 15, 15
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
fn colour_256_to_16(colour: i16) -> i16 {
|
||||||
|
if colour == -1 {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return TABLE_256_TO_16[colour as usize] as i16;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_pair(fg: i16, bg: i16) -> attr_t {
|
||||||
|
let mut dict = COLOURS_DICT.lock().unwrap();
|
||||||
|
if let Some(val) = dict.get(&(fg, bg)) {
|
||||||
|
return COLOR_PAIR(*val);
|
||||||
|
}
|
||||||
|
let mut pair_mut = NEXT_PAIR.lock().unwrap();
|
||||||
|
let pair = *pair_mut;
|
||||||
|
init_pair(pair, fg, bg);
|
||||||
|
dict.insert((fg, bg), pair);
|
||||||
|
*pair_mut += 1;
|
||||||
|
COLOR_PAIR(pair)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Takes a color tuple (as defined at the top of this file) and
|
||||||
|
/// returns a valid curses attr that can be passed directly to attron() or attroff()
|
||||||
|
pub fn curses_attr(mut fg: i16, mut bg: i16, mut attrs: EnumSet<Attr>) -> attr_t {
|
||||||
|
if COLORS() < 256 {
|
||||||
|
// We are not in a term supporting 256 colors, so we convert
|
||||||
|
// colors to numbers between -1 and 8.
|
||||||
|
fg = colour_256_to_16(fg);
|
||||||
|
bg = colour_256_to_16(bg);
|
||||||
|
if fg >= 8 {
|
||||||
|
fg -= 8;
|
||||||
|
attrs.insert(Attr::Bold);
|
||||||
|
}
|
||||||
|
if bg >= 8 {
|
||||||
|
bg -= 8;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let mut pair = get_pair(fg, bg);
|
||||||
|
for attr in attrs.iter() {
|
||||||
|
pair |= attr.get_attron();
|
||||||
|
}
|
||||||
|
pair
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn none() {
|
||||||
|
let attrs = "";
|
||||||
|
let expected = EnumSet::new();
|
||||||
|
let received = parse_attrs(attrs).unwrap();
|
||||||
|
assert_eq!(received.1, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bold_twice() {
|
||||||
|
let attrs = "bb";
|
||||||
|
let mut expected = EnumSet::new();
|
||||||
|
expected.insert(Attr::Bold);
|
||||||
|
let received = parse_attrs(attrs).unwrap();
|
||||||
|
assert_eq!(received.1, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn all() {
|
||||||
|
let attrs = "baiu";
|
||||||
|
let mut expected = EnumSet::new();
|
||||||
|
expected.insert(Attr::Bold);
|
||||||
|
expected.insert(Attr::Blink);
|
||||||
|
expected.insert(Attr::Italic);
|
||||||
|
expected.insert(Attr::Underline);
|
||||||
|
let received = parse_attrs(attrs).unwrap();
|
||||||
|
assert_eq!(received.1, expected);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue