summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.toml5
-rw-r--r--poezio/libpoezio.pyi2
-rwxr-xr-xpoezio/theming.py80
-rw-r--r--src/lib.rs49
-rw-r--r--src/theming.rs161
5 files changed, 221 insertions, 76 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 3beed930..27257012 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -8,6 +8,11 @@ authors = [
[dependencies]
cpython = "0.7"
+nom = "4"
+chrono = "0.4"
+ncurses = "5"
+lazy_static = "1"
+enum-set = "0.0"
[lib]
crate-type = ["cdylib"]
diff --git a/poezio/libpoezio.pyi b/poezio/libpoezio.pyi
new file mode 100644
index 00000000..e02e0a0f
--- /dev/null
+++ b/poezio/libpoezio.pyi
@@ -0,0 +1,2 @@
+
+def to_curses_attr(fg: int, bg: int, attrs: str) -> int: ...
diff --git a/poezio/theming.py b/poezio/theming.py
index 712a44ab..446455e0 100755
--- a/poezio/theming.py
+++ b/poezio/theming.py
@@ -76,8 +76,8 @@ import functools
from typing import Dict, List, Union, Tuple, Optional, cast
from pathlib import Path
from os import path
-from poezio import colors, xdg
from datetime import datetime
+from poezio import colors, xdg, libpoezio
from importlib import machinery
finder = machinery.PathFinder()
@@ -399,43 +399,9 @@ class Theme:
# This is the default theme object, used if no theme is defined in the conf
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] = []
-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:
"""
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)
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
returns a valid curses attr that can be passed directly to attron() or attroff()
"""
# extract the color from that tuple
- colors: Union[Tuple[int, int], Tuple[int, int, str]]
- if len(color_tuple) == 3:
- 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
-
+ attrs = '' if len(ccolors) < 3 else ccolors[2] # type: ignore
+ return libpoezio.to_curses_attr(ccolors[0], ccolors[1], attrs)
def get_theme() -> Theme:
"""
diff --git a/src/lib.rs b/src/lib.rs
index d261b89e..fae54570 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,6 +1,55 @@
#[macro_use]
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| {
+ m.add(
+ py,
+ "to_curses_attr",
+ py_fn!(py, to_curses_attr(fg: i16, bg: i16, attrs: &str)),
+ )?;
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))
+}
diff --git a/src/theming.rs b/src/theming.rs
new file mode 100644
index 00000000..ff108663
--- /dev/null
+++ b/src/theming.rs
@@ -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);
+ }
+}