mirror of
https://github.com/apognu/tuigreet.git
synced 2024-10-26 08:34:57 +03:00
Support color ANSI escape sequences in /etc/issue.
This commit is contained in:
parent
51e7a60b7a
commit
2cc9fd9f97
546
Cargo.lock
generated
546
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -10,6 +10,7 @@ default = []
|
||||
nsswrapper = []
|
||||
|
||||
[dependencies]
|
||||
ansi-to-tui = "4.1.0"
|
||||
chrono = { version = "^0.4", features = ["unstable-locales"] }
|
||||
crossterm = { version = "^0.27", features = ["event-stream"] }
|
||||
futures = "0.3"
|
||||
@ -24,11 +25,11 @@ lazy_static = "^1.4"
|
||||
nix = { version = "^0.28", features = ["feature"] }
|
||||
tui = { package = "ratatui", version = "^0.26", default-features = false, features = [
|
||||
"crossterm",
|
||||
"unstable"
|
||||
] }
|
||||
rust-embed = "^8.0"
|
||||
rust-ini = "^0.21"
|
||||
smart-default = "^0.7"
|
||||
textwrap = "^0.16"
|
||||
tokio = { version = "^1.2", default-features = false, features = [
|
||||
"macros",
|
||||
"rt-multi-thread",
|
||||
|
@ -63,6 +63,9 @@ pub fn get_issue() -> Option<String> {
|
||||
.replace("\\v", uts.version().to_str().unwrap_or(""))
|
||||
.replace("\\n", uts.nodename().to_str().unwrap_or(""))
|
||||
.replace("\\m", uts.machine().to_str().unwrap_or(""))
|
||||
.replace("\\x1b", "\x1b")
|
||||
.replace("\\033", "\x1b")
|
||||
.replace("\\e", "\x1b")
|
||||
.replace("\\\\", "\\"),
|
||||
),
|
||||
|
||||
|
@ -62,7 +62,6 @@ pub fn output(buffer: &Arc<Mutex<Buffer>>) -> String {
|
||||
for cells in buffer.content.chunks(buffer.area.width as usize) {
|
||||
let mut overwritten = vec![];
|
||||
let mut skip: usize = 0;
|
||||
view.push('"');
|
||||
for (x, c) in cells.iter().enumerate() {
|
||||
if skip == 0 {
|
||||
view.push_str(c.symbol());
|
||||
@ -71,7 +70,6 @@ pub fn output(buffer: &Arc<Mutex<Buffer>>) -> String {
|
||||
}
|
||||
skip = std::cmp::max(skip, c.symbol().width()).saturating_sub(1);
|
||||
}
|
||||
view.push('"');
|
||||
if !overwritten.is_empty() {
|
||||
write!(&mut view, " Hidden by multi-width symbols: {overwritten:?}").unwrap();
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
mod backend;
|
||||
mod output;
|
||||
|
||||
use std::{
|
||||
panic,
|
||||
@ -24,9 +25,12 @@ use crate::{
|
||||
Greeter,
|
||||
};
|
||||
|
||||
pub use self::backend::{output, TestBackend};
|
||||
pub(super) use self::{
|
||||
backend::{output, TestBackend},
|
||||
output::*,
|
||||
};
|
||||
|
||||
pub struct IntegrationRunner(Arc<RwLock<_IntegrationRunner>>);
|
||||
pub(super) struct IntegrationRunner(Arc<RwLock<_IntegrationRunner>>);
|
||||
|
||||
struct _IntegrationRunner {
|
||||
server: Option<JoinHandle<()>>,
|
||||
@ -45,9 +49,13 @@ impl Clone for IntegrationRunner {
|
||||
|
||||
impl IntegrationRunner {
|
||||
pub async fn new(opts: SessionOptions, builder: Option<fn(&mut Greeter)>) -> IntegrationRunner {
|
||||
IntegrationRunner::new_with_size(opts, builder, (200, 40)).await
|
||||
}
|
||||
|
||||
pub async fn new_with_size(opts: SessionOptions, builder: Option<fn(&mut Greeter)>, size: (u16, u16)) -> IntegrationRunner {
|
||||
let socket = NamedTempFile::new().unwrap().into_temp_path().to_path_buf();
|
||||
|
||||
let (backend, buffer, tick) = TestBackend::new(200, 200);
|
||||
let (backend, buffer, tick) = TestBackend::new(size.0, size.1);
|
||||
let events = Events::new().await;
|
||||
let sender = events.sender();
|
||||
|
||||
@ -159,8 +167,8 @@ impl IntegrationRunner {
|
||||
self.0.write().await.tick.recv().await;
|
||||
}
|
||||
|
||||
pub async fn output(&self) -> String {
|
||||
output(&self.0.read().await.buffer)
|
||||
pub async fn output(&self) -> Output {
|
||||
Output(output(&self.0.read().await.buffer))
|
||||
}
|
||||
}
|
||||
|
||||
|
26
src/integration/common/output.rs
Normal file
26
src/integration/common/output.rs
Normal file
@ -0,0 +1,26 @@
|
||||
use std::ops::Deref;
|
||||
|
||||
pub(in crate::integration) struct Output(pub String);
|
||||
|
||||
impl Deref for Output {
|
||||
type Target = String;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl Output {
|
||||
pub fn debug_print(&self) {
|
||||
for line in self.lines() {
|
||||
println!("{}", line);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn debug_inspect(&self) {
|
||||
for line in self.lines() {
|
||||
println!("{:?}", line.as_bytes().iter().map(|c| *c as char).collect::<Vec<char>>());
|
||||
}
|
||||
}
|
||||
}
|
@ -34,6 +34,41 @@ async fn show_greet() {
|
||||
runner.join_until_end(events).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn show_wrapped_greet() {
|
||||
let opts = SessionOptions {
|
||||
username: "apognu".to_string(),
|
||||
password: "password".to_string(),
|
||||
mfa: false,
|
||||
};
|
||||
|
||||
let mut runner = IntegrationRunner::new_with_size(
|
||||
opts,
|
||||
Some(|greeter| {
|
||||
greeter.greeting = Some("Lorem \x1b[31mipsum dolor sit amet".to_string());
|
||||
}),
|
||||
(20, 20),
|
||||
)
|
||||
.await;
|
||||
|
||||
let events = tokio::task::spawn({
|
||||
let mut runner = runner.clone();
|
||||
|
||||
async move {
|
||||
runner.wait_for_render().await;
|
||||
|
||||
let output = runner.output().await;
|
||||
|
||||
assert!(output.contains("┌ Authenticate into┐"));
|
||||
assert!(output.contains("│ Lorem ipsum │"));
|
||||
assert!(output.contains("│ dolor sit amet │"));
|
||||
assert!(output.contains("└──────────────────┘"));
|
||||
}
|
||||
});
|
||||
|
||||
runner.join_until_end(events).await;
|
||||
}
|
||||
|
||||
const TIME_FORMAT: &str = "%Y-%m-%dT%H:%M:%S";
|
||||
|
||||
// TODO
|
||||
|
@ -323,8 +323,6 @@ mod test {
|
||||
let mut greeter = Greeter::default();
|
||||
greeter.xsession_wrapper = Some("startx /usr/bin/env".into());
|
||||
|
||||
println!("{:?}", greeter.xsession_wrapper);
|
||||
|
||||
let session = Session {
|
||||
slug: Some("thede".to_string()),
|
||||
name: "Session1".into(),
|
||||
|
@ -3,14 +3,14 @@ use std::error::Error;
|
||||
use rand::{prelude::StdRng, Rng, SeedableRng};
|
||||
use tui::{
|
||||
layout::{Alignment, Constraint, Direction, Layout, Rect},
|
||||
text::{Span, Text},
|
||||
text::Span,
|
||||
widgets::{Block, BorderType, Borders, Paragraph},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
info::get_hostname,
|
||||
ui::{prompt_value, util::*, Frame},
|
||||
Greeter, Mode, SecretDisplay, GreetAlign
|
||||
GreetAlign, Greeter, Mode, SecretDisplay,
|
||||
};
|
||||
|
||||
use super::common::style::Themed;
|
||||
@ -30,7 +30,7 @@ pub fn draw(greeter: &mut Greeter, f: &mut Frame) -> Result<(u16, u16), Box<dyn
|
||||
let greeting_alignment = match greeter.greet_align() {
|
||||
GreetAlign::Center => Alignment::Center,
|
||||
GreetAlign::Left => Alignment::Left,
|
||||
GreetAlign::Right => Alignment::Right
|
||||
GreetAlign::Right => Alignment::Right,
|
||||
};
|
||||
|
||||
let container = Rect::new(x, y, width, height);
|
||||
@ -62,9 +62,8 @@ pub fn draw(greeter: &mut Greeter, f: &mut Frame) -> Result<(u16, u16), Box<dyn
|
||||
let chunks = Layout::default().direction(Direction::Vertical).constraints(constraints.as_ref()).split(frame);
|
||||
let cursor = chunks[USERNAME_INDEX];
|
||||
|
||||
if let Some(greeting) = &greeting {
|
||||
let greeting_text = greeting.trim_end();
|
||||
let greeting_label = Paragraph::new(greeting_text).alignment(greeting_alignment).style(theme.of(&[Themed::Greet]));
|
||||
if let Some(greeting) = greeting {
|
||||
let greeting_label = greeting.alignment(greeting_alignment).style(theme.of(&[Themed::Greet]));
|
||||
|
||||
f.render_widget(greeting_label, chunks[GREETING_INDEX]);
|
||||
}
|
||||
@ -137,8 +136,7 @@ pub fn draw(greeter: &mut Greeter, f: &mut Frame) -> Result<(u16, u16), Box<dyn
|
||||
}
|
||||
|
||||
if let Some(message) = message {
|
||||
let message_text = Text::from(message);
|
||||
let message = Paragraph::new(message_text).alignment(Alignment::Center);
|
||||
let message = message.alignment(Alignment::Center);
|
||||
|
||||
f.render_widget(message, Rect::new(x, y + height, width, message_height));
|
||||
}
|
||||
|
@ -1,4 +1,9 @@
|
||||
use tui::prelude::Rect;
|
||||
use ansi_to_tui::IntoText;
|
||||
use tui::{
|
||||
prelude::Rect,
|
||||
text::Text,
|
||||
widgets::{Paragraph, Wrap},
|
||||
};
|
||||
|
||||
use crate::{Greeter, Mode};
|
||||
|
||||
@ -96,25 +101,31 @@ pub fn get_cursor_offset(greeter: &mut Greeter, length: usize) -> i16 {
|
||||
offset
|
||||
}
|
||||
|
||||
pub fn get_greeting_height(greeter: &Greeter, padding: u16, fallback: u16) -> (Option<String>, u16) {
|
||||
pub fn get_greeting_height(greeter: &Greeter, padding: u16, fallback: u16) -> (Option<Paragraph>, u16) {
|
||||
if let Some(greeting) = &greeter.greeting {
|
||||
let width = greeter.width();
|
||||
let wrapped = textwrap::fill(greeting, (width - (2 * padding)) as usize);
|
||||
let height = wrapped.trim_end().matches('\n').count();
|
||||
|
||||
(Some(wrapped), height as u16 + 2)
|
||||
let text = match greeting.clone().trim().into_text() {
|
||||
Ok(text) => text,
|
||||
Err(_) => Text::raw(greeting),
|
||||
};
|
||||
|
||||
let paragraph = Paragraph::new(text.clone()).wrap(Wrap { trim: true });
|
||||
let height = paragraph.line_count(width - (2 * padding)) + 1;
|
||||
|
||||
(Some(paragraph), height as u16)
|
||||
} else {
|
||||
(None, fallback)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_message_height(greeter: &Greeter, padding: u16, fallback: u16) -> (Option<String>, u16) {
|
||||
pub fn get_message_height(greeter: &Greeter, padding: u16, fallback: u16) -> (Option<Paragraph>, u16) {
|
||||
if let Some(message) = &greeter.message {
|
||||
let width = greeter.width();
|
||||
let wrapped = textwrap::fill(message.trim_end(), width as usize - 4);
|
||||
let height = wrapped.trim_end().matches('\n').count();
|
||||
let paragraph = Paragraph::new(message.trim_end()).wrap(Wrap { trim: true });
|
||||
let height = paragraph.line_count(width - 4);
|
||||
|
||||
(Some(wrapped), height as u16 + padding)
|
||||
(Some(paragraph), height as u16 + padding)
|
||||
} else {
|
||||
(None, fallback)
|
||||
}
|
||||
@ -122,7 +133,12 @@ pub fn get_message_height(greeter: &Greeter, padding: u16, fallback: u16) -> (Op
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use tui::prelude::Rect;
|
||||
use tui::{
|
||||
prelude::Rect,
|
||||
style::{Color, Style},
|
||||
text::{Line, Span, Text},
|
||||
widgets::{Paragraph, Wrap},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
ui::util::{get_greeting_height, get_height},
|
||||
@ -243,24 +259,58 @@ mod test {
|
||||
#[test]
|
||||
fn greeting_height_one_line() {
|
||||
let mut greeter = Greeter::default();
|
||||
greeter.config = Greeter::options().parse(&["--width", "10", "--container-padding", "1"]).ok();
|
||||
greeter.greeting = Some("Hello".into());
|
||||
greeter.config = Greeter::options().parse(&["--width", "15", "--container-padding", "1"]).ok();
|
||||
greeter.greeting = Some("Hello World".into());
|
||||
|
||||
let (text, width) = get_greeting_height(&greeter, 1, 0);
|
||||
let (_, height) = get_greeting_height(&greeter, 1, 0);
|
||||
|
||||
assert!(matches!(text.as_deref(), Some("Hello")));
|
||||
assert_eq!(width, 2);
|
||||
assert_eq!(height, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn greeting_height_two_lines() {
|
||||
let mut greeter = Greeter::default();
|
||||
greeter.config = Greeter::options().parse(&["--width", "10", "--container-padding", "1"]).ok();
|
||||
greeter.config = Greeter::options().parse(&["--width", "8", "--container-padding", "1"]).ok();
|
||||
greeter.greeting = Some("Hello World".into());
|
||||
|
||||
let (text, width) = get_greeting_height(&greeter, 1, 0);
|
||||
let (_, height) = get_greeting_height(&greeter, 1, 0);
|
||||
|
||||
assert!(matches!(text.as_deref(), Some("Hello\nWorld")));
|
||||
assert_eq!(width, 3);
|
||||
assert_eq!(height, 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ansi_greeting_height_one_line() {
|
||||
let mut greeter = Greeter::default();
|
||||
greeter.config = Greeter::options().parse(&["--width", "15", "--container-padding", "1"]).ok();
|
||||
greeter.greeting = Some("\x1b[31mHello\x1b[0m World".into());
|
||||
|
||||
let (text, height) = get_greeting_height(&greeter, 1, 0);
|
||||
|
||||
let expected = Paragraph::new(Text::from(vec![Line::from(vec![
|
||||
Span::styled("Hello", Style::default().fg(Color::Red)),
|
||||
Span::styled(" World", Style::reset()),
|
||||
])]))
|
||||
.wrap(Wrap { trim: true });
|
||||
|
||||
assert_eq!(text, Some(expected));
|
||||
assert_eq!(height, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ansi_greeting_height_two_lines() {
|
||||
let mut greeter = Greeter::default();
|
||||
greeter.config = Greeter::options().parse(&["--width", "8", "--container-padding", "1"]).ok();
|
||||
greeter.greeting = Some("\x1b[31mHello\x1b[0m World".into());
|
||||
|
||||
let (text, height) = get_greeting_height(&greeter, 1, 0);
|
||||
|
||||
let expected = Paragraph::new(Text::from(vec![Line::from(vec![
|
||||
Span::styled("Hello", Style::default().fg(Color::Red)),
|
||||
Span::styled(" World", Style::reset()),
|
||||
])]))
|
||||
.wrap(Wrap { trim: true });
|
||||
|
||||
assert_eq!(text, Some(expected));
|
||||
assert_eq!(height, 3);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user