summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorManos Pitsidianakis <el13635@mail.ntua.gr>2022-09-01 21:14:17 +0300
committerManos Pitsidianakis <el13635@mail.ntua.gr>2022-09-01 22:24:01 +0300
commita73885acb14cd94d4a6a54ebd5b39a001d7e21e1 (patch)
tree3bdd00c5500c97197ab71c16557fe8cf1584e3b4
parent480000ebbb67a80181fd27762ca649acf13df0f3 (diff)
downloadmeli-a73885acb14cd94d4a6a54ebd5b39a001d7e21e1.zip
Improve embed terminal
- Add character attribute support - Add cursor key mode support - Fix buggy set fg / bg sequences And added a bin under tools to test arbitrary apps using the embedded terminal: cargo run -p tools --bin embed -- "htop" 2> .htop.debug.log
-rw-r--r--Cargo.lock5
-rw-r--r--Cargo.toml4
-rw-r--r--src/components/mail/compose.rs2
-rw-r--r--src/conf/listing.rs38
-rw-r--r--src/jobs.rs5
-rw-r--r--src/lib.rs80
-rw-r--r--src/main.rs53
-rw-r--r--src/terminal.rs2
-rw-r--r--src/terminal/cells.rs48
-rw-r--r--src/terminal/color.rs4
-rw-r--r--src/terminal/embed.rs21
-rw-r--r--src/terminal/embed/grid.rs456
-rw-r--r--src/terminal/position.rs33
-rw-r--r--tools/Cargo.toml14
-rw-r--r--tools/src/embed.rs581
15 files changed, 1208 insertions, 138 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 0fadcdc5..a97432c9 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1880,7 +1880,12 @@ dependencies = [
name = "tools"
version = "0.4.1"
dependencies = [
+ "crossbeam",
+ "meli",
"melib",
+ "nix",
+ "signal-hook",
+ "signal-hook-registry",
]
[[package]]
diff --git a/Cargo.toml b/Cargo.toml
index edc07c5f..8570b87f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -17,6 +17,10 @@ default-run = "meli"
name = "meli"
path = "src/main.rs"
+[lib]
+name = "meli"
+path = "src/lib.rs"
+
#[[bin]]
#name = "managesieve-meli"
#path = "src/managesieve.rs"
diff --git a/src/components/mail/compose.rs b/src/components/mail/compose.rs
index fb33edd0..3e3acdf3 100644
--- a/src/components/mail/compose.rs
+++ b/src/components/mail/compose.rs
@@ -1414,7 +1414,7 @@ impl Component for Composer {
use std::io::Write;
if let Some(ref mut embed) = self.embed {
let mut embed_guard = embed.lock().unwrap();
- if embed_guard.stdin.write_all(b).is_err() {
+ if embed_guard.write_all(b).is_err() {
match embed_guard.is_active() {
Ok(WaitStatus::Exited(_, exit_code)) => {
drop(embed_guard);
diff --git a/src/conf/listing.rs b/src/conf/listing.rs
index e20441d2..1160646d 100644
--- a/src/conf/listing.rs
+++ b/src/conf/listing.rs
@@ -29,25 +29,31 @@ use melib::{MeliError, Result};
/// Tree decoration examples:
///
///```no_run
-///const HAS_SIBLING: &'static str = " ┃";
-///const NO_SIBLING: &'static str = " ";
-///const HAS_SIBLING_LEAF: &'static str = " ┣━";
-///const NO_SIBLING_LEAF: &'static str = " ┗━";
+///const HAS_SIBLING: &str = " ┃";
+///const NO_SIBLING: &str = " ";
+///const HAS_SIBLING_LEAF: &str = " ┣━";
+///const NO_SIBLING_LEAF: &str = " ┗━";
+///```
///
-///const HAS_SIBLING: &'static str = " |";
-///const NO_SIBLING: &'static str = " ";
-///const HAS_SIBLING_LEAF: &'static str = " |\\_";
-///const NO_SIBLING_LEAF: &'static str = " \\_";
+///```no_run
+///const HAS_SIBLING: &str = " |";
+///const NO_SIBLING: &str = " ";
+///const HAS_SIBLING_LEAF: &str = " |\\_";
+///const NO_SIBLING_LEAF: &str = " \\_";
+///```
///
-///const HAS_SIBLING: &'static str = " ";
-///const NO_SIBLING: &'static str = " ";
-///const HAS_SIBLING_LEAF: &'static str = " ";
-///const NO_SIBLING_LEAF: &'static str = " ";
+///```no_run
+///const HAS_SIBLING: &str = " ";
+///const NO_SIBLING: &str = " ";
+///const HAS_SIBLING_LEAF: &str = " ";
+///const NO_SIBLING_LEAF: &str = " ";
+///```
///
-///const HAS_SIBLING: &'static str = " │";
-///const NO_SIBLING: &'static str = " ";
-///const HAS_SIBLING_LEAF: &'static str = " ├─";
-///const NO_SIBLING_LEAF: &'static str = " ╰─";
+///```no_run
+///const HAS_SIBLING: &str = " │";
+///const NO_SIBLING: &str = " ";
+///const HAS_SIBLING_LEAF: &str = " ├─";
+///const NO_SIBLING_LEAF: &str = " ╰─";
///```
#[derive(Debug, Deserialize, Clone, Serialize)]
#[serde(deny_unknown_fields)]
diff --git a/src/jobs.rs b/src/jobs.rs
index f37dc763..d201c5ef 100644
--- a/src/jobs.rs
+++ b/src/jobs.rs
@@ -20,11 +20,6 @@
*/
//! Async job executor thread pool
-//!
-//! ## Usage
-//! ```no_run
-//! let (channel, handle, job_id) = job_executor.spawn(job);
-//! ```
use melib::smol;
use melib::uuid::Uuid;
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 00000000..b9726211
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,80 @@
+/*
+ * meli - lib.rs
+ *
+ * Copyright 2017-2022 Manos Pitsidianakis
+ *
+ * This file is part of meli.
+ *
+ * meli is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * meli is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with meli. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+//!
+//! This crate contains the frontend stuff of the application. The application entry way on
+//! `src/bin.rs` creates an event loop and passes input to a thread.
+//!
+//! The mail handling stuff is done in the `melib` crate which includes all backend needs. The
+//! split is done to theoretically be able to create different frontends with the same innards.
+//!
+
+use std::alloc::System;
+pub use std::collections::VecDeque;
+pub use std::path::PathBuf;
+
+#[macro_use]
+extern crate serde_derive;
+extern crate linkify;
+pub use melib::uuid;
+
+pub extern crate bitflags;
+pub extern crate serde_json;
+#[macro_use]
+pub extern crate smallvec;
+pub extern crate termion;
+
+pub use structopt::StructOpt;
+
+#[global_allocator]
+static GLOBAL: System = System;
+
+#[macro_use]
+extern crate melib;
+pub use melib::*;
+
+#[macro_use]
+pub mod types;
+pub use crate::types::*;
+
+#[macro_use]
+pub mod terminal;
+pub use crate::terminal::*;
+
+#[macro_use]
+pub mod command;
+pub use crate::command::*;
+
+pub mod state;
+pub use crate::state::*;
+
+pub mod components;
+pub use crate::components::*;
+
+#[macro_use]
+pub mod conf;
+pub use crate::conf::*;
+
+#[cfg(feature = "sqlite3")]
+pub mod sqlite3;
+
+pub mod jobs;
+pub mod mailcap;
diff --git a/src/main.rs b/src/main.rs
index 510a5908..24e741cf 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -27,58 +27,7 @@
//! split is done to theoretically be able to create different frontends with the same innards.
//!
-use std::alloc::System;
-use std::collections::VecDeque;
-use std::path::PathBuf;
-
-#[macro_use]
-extern crate serde_derive;
-extern crate linkify;
-pub(crate) use melib::uuid;
-
-extern crate bitflags;
-extern crate serde_json;
-#[macro_use]
-extern crate smallvec;
-extern crate termion;
-
-use structopt::StructOpt;
-
-#[global_allocator]
-static GLOBAL: System = System;
-
-#[macro_use]
-extern crate melib;
-use melib::*;
-
-#[macro_use]
-pub mod types;
-use crate::types::*;
-
-#[macro_use]
-pub mod terminal;
-use crate::terminal::*;
-
-#[macro_use]
-pub mod command;
-use crate::command::*;
-
-pub mod state;
-use crate::state::*;
-
-pub mod components;
-use crate::components::*;
-
-#[macro_use]
-pub mod conf;
-use crate::conf::*;
-
-#[cfg(feature = "sqlite3")]
-pub mod sqlite3;
-
-pub mod jobs;
-pub mod mailcap;
-
+use meli::*;
use std::os::raw::c_int;
fn notify(
diff --git a/src/terminal.rs b/src/terminal.rs
index fb5c217b..57e3774e 100644
--- a/src/terminal.rs
+++ b/src/terminal.rs
@@ -312,7 +312,7 @@ mod braille {
/// ];
///
/// for lines in BEE.chunks(12) {
- /// let iter = ui::BraillePixelIter::from(lines);
+ /// let iter = meli::BraillePixelIter::from(lines);
/// for b in iter {
/// print!("{}", b);
/// }
diff --git a/src/terminal/cells.rs b/src/terminal/cells.rs
index ba739a62..914cbe56 100644
--- a/src/terminal/cells.rs
+++ b/src/terminal/cells.rs
@@ -197,15 +197,6 @@ impl CellBuffer {
/// Returns a reference to the `Cell` at the given coordinates, or `None` if the index is out of
/// bounds.
- ///
- /// # Examples
- ///
- /// ```no_run
- ///
- /// let mut term = Terminal::new().unwrap();
- ///
- /// let a_cell = term.get(5, 5);
- /// ```
pub fn get(&self, x: usize, y: usize) -> Option<&Cell> {
match self.pos_to_index(x, y) {
Some(i) => self.cellvec().get(i),
@@ -215,15 +206,6 @@ impl CellBuffer {
/// Returns a mutable reference to the `Cell` at the given coordinates, or `None` if the index
/// is out of bounds.
- ///
- /// # Examples
- ///
- /// ```no_run
- ///
- /// let mut term = Terminal::new().unwrap();
- ///
- /// let a_mut_cell = term.get_mut(5, 5);
- /// ```
pub fn get_mut(&mut self, x: usize, y: usize) -> Option<&mut Cell> {
match self.pos_to_index(x, y) {
Some(i) => self.cellvec_mut().get_mut(i),
@@ -518,6 +500,8 @@ impl Cell {
/// # Examples
///
/// ```no_run
+ /// use meli::{Color, Attr, Cell};
+ ///
/// let cell = Cell::new('x', Color::Default, Color::Green, Attr::DEFAULT);
/// assert_eq!(cell.ch(), 'x');
/// assert_eq!(cell.fg(), Color::Default);
@@ -542,6 +526,8 @@ impl Cell {
/// # Examples
///
/// ```no_run
+ /// use meli::{Color, Attr, Cell};
+ ///
/// let mut cell = Cell::with_char('x');
/// assert_eq!(cell.ch(), 'x');
/// assert_eq!(cell.fg(), Color::Default);
@@ -557,6 +543,8 @@ impl Cell {
/// # Examples
///
/// ```no_run
+ /// use meli::{Color, Attr, Cell};
+ ///
/// let mut cell = Cell::with_style(Color::Default, Color::Red, Attr::BOLD);
/// assert_eq!(cell.fg(), Color::Default);
/// assert_eq!(cell.bg(), Color::Red);
@@ -572,6 +560,8 @@ impl Cell {
/// # Examples
///
/// ```no_run
+ /// use meli::Cell;
+ ///
/// let mut cell = Cell::with_char('x');
/// assert_eq!(cell.ch(), 'x');
/// ```
@@ -584,6 +574,8 @@ impl Cell {
/// # Examples
///
/// ```no_run
+ /// use meli::Cell;
+ ///
/// let mut cell = Cell::with_char('x');
/// assert_eq!(cell.ch(), 'x');
///
@@ -603,6 +595,8 @@ impl Cell {
/// # Examples
///
/// ```no_run
+ /// use meli::{Color, Attr, Cell};
+ ///
/// let mut cell = Cell::with_style(Color::Blue, Color::Default, Attr::DEFAULT);
/// assert_eq!(cell.fg(), Color::Blue);
/// ```
@@ -615,6 +609,8 @@ impl Cell {
/// # Examples
///
/// ```no_run
+ /// use meli::{Color, Cell};
+ ///
/// let mut cell = Cell::default();
/// assert_eq!(cell.fg(), Color::Default);
///
@@ -633,6 +629,8 @@ impl Cell {
/// # Examples
///
/// ```no_run
+ /// use meli::{Attr, Color, Cell};
+ ///
/// let mut cell = Cell::with_style(Color::Default, Color::Green, Attr::DEFAULT);
/// assert_eq!(cell.bg(), Color::Green);
/// ```
@@ -645,6 +643,8 @@ impl Cell {
/// # Examples
///
/// ```no_run
+ /// use meli::{Cell, Color};
+ ///
/// let mut cell = Cell::default();
/// assert_eq!(cell.bg(), Color::Default);
///
@@ -708,6 +708,8 @@ impl Default for Cell {
/// # Examples
///
/// ```no_run
+ /// use meli::{Color, Cell};
+ ///
/// let mut cell = Cell::default();
/// assert_eq!(cell.ch(), ' ');
/// assert_eq!(cell.fg(), Color::Default);
@@ -728,6 +730,8 @@ bitflags::bitflags! {
/// # Examples
///
/// ```no_run
+ /// use meli::Attr;
+ ///
/// // Default attribute.
/// let def = Attr::DEFAULT;
///
@@ -1234,6 +1238,8 @@ pub fn clear_area(grid: &mut CellBuffer, area: Area, attributes: crate::conf::Th
/// `RowIterator` can be created via the `CellBuffer::row_iter` method and can be returned by
/// `BoundsIterator` which iterates each row.
/// ```no_run
+/// # let mut grid = meli::CellBuffer::new(1, 1, meli::Cell::default());
+/// # let x = 0;
/// for c in grid.row_iter(
/// x..(x + 11),
/// 0,
@@ -1248,9 +1254,13 @@ pub struct RowIterator {
/// `BoundsIterator` iterates each row returning a `RowIterator`.
/// ```no_run
+/// # let mut grid = meli::CellBuffer::new(1, 1, meli::Cell::default());
+/// # let area = ((0, 0), (1, 1));
/// /* Visit each `Cell` in `area`. */
-/// for c in grid.bounds_iter(area) {
+/// for row in grid.bounds_iter(area) {
+/// for c in row {
/// grid[c].set_ch('w');
+/// }
/// }
/// ```
pub struct BoundsIterator {
diff --git a/src/terminal/color.rs b/src/terminal/color.rs
index 872394d9..6c915e4f 100644
--- a/src/terminal/color.rs
+++ b/src/terminal/color.rs
@@ -34,7 +34,9 @@ use termion::color::{AnsiValue, Rgb as TermionRgb};
///
/// # Examples
///
-/// ```no_run
+/// ```
+/// use meli::Color;
+///
/// // The default color.
/// let default = Color::Default;
///
diff --git a/src/terminal/embed.rs b/src/terminal/embed.rs
index 52bef17c..d9888410 100644
--- a/src/terminal/embed.rs
+++ b/src/terminal/embed.rs
@@ -210,6 +210,27 @@ pub enum State {
Csi1(SmallVec<[u8; 8]>),
Csi2(SmallVec<[u8; 8]>, SmallVec<[u8; 8]>),
Csi3(SmallVec<[u8; 8]>, SmallVec<[u8; 8]>, SmallVec<[u8; 8]>),
+ Csi4(
+ SmallVec<[u8; 8]>,
+ SmallVec<[u8; 8]>,
+ SmallVec<[u8; 8]>,
+ SmallVec<[u8; 8]>,
+ ),
+ Csi5(
+ SmallVec<[u8; 8]>,
+ SmallVec<[u8; 8]>,
+ SmallVec<[u8; 8]>,
+ SmallVec<[u8; 8]>,
+ SmallVec<[u8; 8]>,
+ ),
+ Csi6(
+ SmallVec<[u8; 8]>,
+ SmallVec<[u8; 8]>,
+ SmallVec<[u8; 8]>,
+ SmallVec<[u8; 8]>,
+ SmallVec<[u8; 8]>,
+ SmallVec<[u8; 8]>,
+ ),
CsiQ(SmallVec<[u8; 8]>),
Normal,
}
diff --git a/src/terminal/embed/grid.rs b/src/terminal/embed/grid.rs
index 0af9fec6..0eb90e18 100644
--- a/src/terminal/embed/grid.rs
+++ b/src/terminal/embed/grid.rs
@@ -53,11 +53,14 @@ pub struct EmbedGrid {
initialized: bool,
fg_color: Color,
bg_color: Color,
+ attrs: Attr,
/// Store the fg/bg color when highlighting the cell where the cursor is so that it can be
/// restored afterwards
prev_fg_color: Option<Color>,
prev_bg_color: Option<Color>,
+ prev_attrs: Option<Attr>,
+ cursor_key_mode: bool, // (DECCKM)
show_cursor: bool,
origin_mode: bool,
auto_wrap_mode: bool,
@@ -73,11 +76,57 @@ pub struct EmbedGrid {
#[derive(Debug)]
pub struct EmbedTerminal {
pub grid: EmbedGrid,
- pub stdin: std::fs::File,
+ stdin: std::fs::File,
/// Pid of the embed process
pub child_pid: nix::unistd::Pid,
}
+impl std::io::Write for EmbedTerminal {
+ fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
+ /*
+
+ Key Normal Application
+ -------------+----------+-------------
+ Cursor Up | CSI A | SS3 A
+ Cursor Down | CSI B | SS3 B
+ Cursor Right | CSI C | SS3 C
+ Cursor Left | CSI D | SS3 D
+ -------------+----------+-------------
+
+ Key Normal Application
+ ---------+----------+-------------
+ Home | CSI H | SS3 H
+ End | CSI F | SS3 F
+ ---------+----------+-------------
+
+ */
+ if self.grid.cursor_key_mode {
+ match buf {
+ &[0x1b, 0x5b, b'A']
+ | &[0x1b, 0x5b, b'B']
+ | &[0x1b, 0x5b, b'C']
+ | &[0x1b, 0x5b, b'D']
+ | &[0x1b, 0x5b, b'H']
+ | &[0x1b, 0x5b, b'F'] => {
+ self.stdin.write_all(&[0x1b, 0x4f, buf[2]])?;
+ Ok(buf.len())
+ }
+ _ => {
+ self.stdin.write_all(buf)?;
+ Ok(buf.len())
+ }
+ }
+ } else {
+ self.stdin.write_all(buf)?;
+ Ok(buf.len())
+ }
+ }
+
+ fn flush(&mut self) -> std::io::Result<()> {
+ self.stdin.flush()
+ }
+}
+
impl EmbedTerminal {
pub fn new(stdin: std::fs::File, child_pid: nix::unistd::Pid) -> Self {
EmbedTerminal {
@@ -156,11 +205,14 @@ impl EmbedGrid {
state: State::Normal,
fg_color: Color::Default,
bg_color: Color::Default,
+ attrs: Attr::DEFAULT,
prev_fg_color: None,
prev_bg_color: None,
+ prev_attrs: None,
show_cursor: true,
auto_wrap_mode: true,
wrap_next: false,
+ cursor_key_mode: false,
origin_mode: false,
codepoints: CodepointBuf::None,
normal_screen,
@@ -219,12 +271,15 @@ impl EmbedGrid {
ref mut state,
ref mut fg_color,
ref mut bg_color,
+ ref mut attrs,
ref mut prev_fg_color,
ref mut prev_bg_color,
+ ref mut prev_attrs,
ref mut codepoints,
ref mut show_cursor,
ref mut auto_wrap_mode,
ref mut wrap_next,
+ ref mut cursor_key_mode,
ref mut origin_mode,
ref mut screen_buffer,
ref mut normal_screen,
@@ -475,6 +530,7 @@ impl EmbedGrid {
grid[cursor_val!()].set_ch(c);
grid[cursor_val!()].set_fg(*fg_color);
grid[cursor_val!()].set_bg(*bg_color);
+ grid[cursor_val!()].set_attrs(*attrs);
match wcwidth(u32::from(c)) {
Some(0) | None => {
/* Skip drawing zero width characters */
@@ -489,6 +545,7 @@ impl EmbedGrid {
grid[cursor_val!()].set_empty(true);
grid[cursor_val!()].set_fg(*fg_color);
grid[cursor_val!()].set_bg(*bg_color);
+ grid[cursor_val!()].set_attrs(*attrs);
}
}
}
@@ -505,8 +562,10 @@ impl EmbedGrid {
//debug!("{}", EscCode::from((&(*state), byte)));
*fg_color = Color::Default;
*bg_color = Color::Default;
+ *attrs = Attr::DEFAULT;
grid[cursor_val!()].set_fg(Color::Default);
grid[cursor_val!()].set_bg(Color::Default);
+ grid[cursor_val!()].set_attrs(Attr::DEFAULT);
*state = State::Normal;
}
(b'C', State::Csi) => {
@@ -523,6 +582,9 @@ impl EmbedGrid {
}
(b'h', State::CsiQ(ref buf)) => {
match buf.as_slice() {
+ b"1" => {
+ *cursor_key_mode = true;
+ }
b"6" => {
*origin_mode = true;
}
@@ -533,13 +595,17 @@ impl EmbedGrid {
*show_cursor = true;
*prev_fg_color = Some(grid[cursor_val!()].fg());
*prev_bg_color = Some(grid[cursor_val!()].bg());
+ *prev_attrs = Some(grid[cursor_val!()].attrs());
grid[cursor_val!()].set_fg(Color::Black);
grid[cursor_val!()].set_bg(Color::White);
+ grid[cursor_val!()].set_attrs(Attr::DEFAULT);
}
b"1047" | b"1049" => {
*screen_buffer = ScreenBuffer::Alternate;
}
- _ => {}
+ _ => {
+ debug!("unknown csi? {:?}", String::from_utf8_lossy(buf.as_slice()));
+ }
}
//debug!("{}", EscCode::from((&(*state), byte)));
@@ -547,6 +613,9 @@ impl EmbedGrid {
}
(b'l', State::CsiQ(ref mut buf)) => {
match buf.as_slice() {
+ b"1" => {
+ *cursor_key_mode = false;
+ }
b"6" => {
*origin_mode = false;
}
@@ -565,11 +634,21 @@ impl EmbedGrid {
} else {
grid[cursor_val!()].set_bg(*bg_color);
}
+ if let Some(attrs) = prev_attrs.take() {
+ grid[cursor_val!()].set_attrs(attrs);
+ } else {
+ grid[cursor_val!()].set_attrs(*attrs);
+ }
}
b"1047" | b"1049" => {
*screen_buffer = ScreenBuffer::Normal;
}
- _ => {}
+ _ => {
+ debug!(
+ "unknown csi? `l` {:?}",
+ String::from_utf8_lossy(buf.as_slice())
+ );
+ }
}
//debug!("{}", EscCode::from((&(*state), byte)));
*state = State::Normal;
@@ -975,14 +1054,63 @@ impl EmbedGrid {
// Character Attributes.
match buf1.as_slice() {
b"0" => {
- *fg_color = Color::Default;
- *bg_color = Color::Default;
+ *attrs = Attr::DEFAULT;
+ }
+ b"1" => {
+ /* bold */
+ *attrs |= Attr::BOLD;
+ }
+ b"2" => {
+ /* faint, dim */
+ *attrs |= Attr::DIM;
+ }
+ b"3" => {
+ /* italicized */
+ *attrs |= Attr::ITALICS;
+ }
+ b"4" => {
+ /* underlined */
+ *attrs |= Attr::UNDERLINE;
+ }
+ b"5" => {
+ /* blink */
+ *attrs |= Attr::BLINK;
+ }
+ b"7" => {
+ /* Inverse */
+ *attrs |= Attr::REVERSE;
+ }
+ b"8" => {
+ /* invisible */
+ *attrs |= Attr::HIDDEN;
+ }
+ b"9" => { /* crossed out */ }
+ b"21" => { /* Doubly-underlined */ }
+ b"22" => {
+ /* Normal (neither bold nor faint), ECMA-48 3rd. */
+ *attrs &= !(Attr::BOLD | Attr::DIM);
}
- b"1" => { /* bold */ }
- b"7" | b"27" => {
- /* Inverse on/off */
- std::mem::swap(&mut (*fg_color), &mut (*bg_color))
+ b"23" => {
+ /* Not italicized, ECMA-48 3rd */
+ *attrs &= !Attr::ITALICS;
}
+ b"24" => {
+ /* Not underlined, ECMA-48 3rd. */
+ *attrs &= !Attr::UNDERLINE;
+ }
+ b"25" => {
+ /* Steady (not blinking), ECMA-48 3rd. */
+ *attrs &= !Attr::BLINK;
+ }
+ b"27" => {
+ /* Positive (not inverse), ECMA-48 3rd. */
+ *attrs &= !Attr::REVERSE;
+ }
+ b"28" => {
+ /* Visible, i.e., not hidden, ECMA-48 3rd, VT300. */
+ *attrs &= !Attr::HIDDEN;
+ }
+ b"29" => { /* Not crossed-out, ECMA-48 3rd. */ }
b"30" => *fg_color = Color::Black,
b"31" => *fg_color = Color::Red,
b"32" => *fg_color = Color::Green,
@@ -991,8 +1119,8 @@ impl EmbedGrid {
b"35" => *fg_color = Color::Magenta,
b"36" => *fg_color = Color::Cyan,
b"37" => *fg_color = Color::White,
-
b"39" => *fg_color = Color::Default,
+
b"40" => *bg_color = Color::Black,
b"41" => *bg_color = Color::Red,
b"42" => *bg_color = Color::Green,
@@ -1001,7 +1129,6 @@ impl EmbedGrid {
b"45" => *bg_color = Color::Magenta,
b"46" => *bg_color = Color::Cyan,
b"47" => *bg_color = Color::White,
-
b"49" => *bg_color = Color::Default,
b"90" => *fg_color = Color::Black,
@@ -1021,24 +1148,79 @@ impl EmbedGrid {
b"105" => *bg_color = Color::Magenta,
b"106" => *bg_color = Color::Cyan,
b"107" => *bg_color = Color::White,
- _ => {}
+ _ => {
+ debug!(
+ "unknown attribute Csi1 {:?} m",
+ String::from_utf8_lossy(buf1.as_slice())
+ );
+ }
}
grid[cursor_val!()].set_fg(*fg_color);
grid[cursor_val!()].set_bg(*bg_color);
+ grid[cursor_val!()].set_attrs(*attrs);
*state = State::Normal;
}
(b'm', State::Csi2(ref buf1, ref buf2)) => {
for b in &[buf1, buf2] {
match b.as_slice() {
b"0" => {
- *fg_color = Color::Default;
- *bg_color = Color::Default;
+ *attrs = Attr::DEFAULT;
+ }
+ b"1" => {
+ /* bold */
+ *attrs |= Attr::BOLD;
+ }
+ b"2" => {
+ /* faint, dim */
+ *attrs |= Attr::DIM;
+ }
+ b"3" => {
+ /* italicized */
+ *attrs |= Attr::ITALICS;
+ }
+ b"4" => {
+ /* underlined */
+ *attrs |= Attr::UNDERLINE;
}
- b"1" => { /* bold */ }
- b"7" | b"27" => {
- /* Inverse on/off */
- std::mem::swap(&mut (*fg_color), &mut (*bg_color))
+ b"5" => {
+ /* blink */
+ *attrs |= Attr::BLINK;
}
+ b"7" => {
+ /* Inverse */
+ *attrs |= Attr::REVERSE;
+ }
+ b"8" => {
+ /* invisible */
+ *attrs |= Attr::HIDDEN;
+ }
+ b"9" => { /* crossed out */ }
+ b"21" => { /* Doubly-underlined */ }
+ b"22" => {
+ /* Normal (neither bold nor faint), ECMA-48 3rd. */
+ *attrs &= !(Attr::BOLD | Attr::DIM);
+ }
+ b"23" => {
+ /* Not italicized, ECMA-48 3rd */
+ *attrs &= !Attr::ITALICS;
+ }
+ b"24" => {
+ /* Not underlined, ECMA-48 3rd. */
+ *attrs &= !Attr::UNDERLINE;
+ }
+ b"25" => {
+ /* Steady (not blinking), ECMA-48 3rd. */
+ *attrs &= !Attr::BLINK;
+ }
+ b"27" => {
+ /* Positive (not inverse), ECMA-48 3rd. */
+ *attrs &= !Attr::REVERSE;
+ }
+ b"28" => {
+ /* Visible, i.e., not hidden, ECMA-48 3rd, VT300. */
+ *attrs &= !Attr::HIDDEN;
+ }
+ b"29" => { /* Not crossed-out, ECMA-48 3rd. */ }
b"30" => *fg_color = Color::Black,
b"31" => *fg_color = Color::Red,
b"32" => *fg_color = Color::Green,
@@ -1047,8 +1229,8 @@ impl EmbedGrid {
b"35" => *fg_color = Color::Magenta,
b"36" => *fg_color = Color::Cyan,
b"37" => *fg_color = Color::White,
-
b"39" => *fg_color = Color::Default,
+
b"40" => *bg_color = Color::Black,
b"41" => *bg_color = Color::Red,
b"42" => *bg_color = Color::Green,
@@ -1057,7 +1239,6 @@ impl EmbedGrid {
b"45" => *bg_color = Color::Magenta,
b"46" => *bg_color = Color::Cyan,
b"47" => *bg_color = Color::White,
-
b"49" => *bg_color = Color::Default,
b"90" => *fg_color = Color::Black,
@@ -1077,11 +1258,17 @@ impl EmbedGrid {
b"105" => *bg_color = Color::Magenta,
b"106" => *bg_color = Color::Cyan,
b"107" => *bg_color = Color::White,
- _ => {}
+ _ => {
+ debug!(
+ "unknown attribute Csi1 {:?} m",
+ String::from_utf8_lossy(buf1.as_slice())
+ );
+ }
}
}
grid[cursor_val!()].set_fg(*fg_color);
grid[cursor_val!()].set_bg(*bg_color);
+ grid[cursor_val!()].set_attrs(*attrs);
*state = State::Normal;
}
(c, State::Csi1(ref mut buf)) if (b'0'..=b'9').contains(&c) || c == b' ' => {
@@ -1200,32 +1387,245 @@ impl EmbedGrid {
grid[cursor_val!()].set_bg(*bg_color);
*state = State::Normal;
}
+ (c, State::Csi3(_, _, ref mut buf)) if (b'0'..=b'9').contains(&c) => {
+ buf.push(c);
+ }
+ (b';', State::Csi3(ref mut buf1_p, ref mut buf2_p, ref mut buf3_p)) => {
+ let buf1 = std::mem::replace(buf1_p, SmallVec::new());
+ let buf2 = std::mem::replace(buf2_p, SmallVec::new());
+ let buf3 = std::mem::replace(buf3_p, SmallVec::new());
+ let buf4 = SmallVec::new();
+ *state = State::Csi4(buf1, buf2, buf3, buf4);
+ }
+ (c, State::Csi4(_, _, _, ref mut buf)) if (b'0'..=b'9').contains(&c) => {
+ buf.push(c);
+ }
+ (b';', State::Csi4(ref mut buf1_p, ref mut buf2_p, ref mut buf3_p, ref mut buf4_p)) => {
+ let buf1 = std::mem::replace(buf1_p, SmallVec::new());
+ let buf2 = std::mem::replace(buf2_p, SmallVec::new());
+ let buf3 = std::mem::replace(buf3_p, SmallVec::new());
+ let buf4 = std::mem::replace(buf4_p, SmallVec::new());
+ let buf5 = SmallVec::new();
+ *state = State::Csi5(buf1, buf2, buf3, buf4, buf5);
+ }
+ (c, State::Csi5(_, _, _, _, ref mut buf)) if (b'0'..=b'9').contains(&c) => {
+ buf.push(c);
+ }
+ (
+ b';',
+ State::Csi5(
+ ref mut buf1_p,
+ ref mut buf2_p,
+ ref mut buf3_p,
+ ref mut buf4_p,
+ ref mut buf5_p,
+ ),
+ ) => {
+ let buf1 = std::mem::replace(buf1_p, SmallVec::new());
+ let buf2 = std::mem::replace(buf2_p, SmallVec::new());
+ let buf3 = std::mem::replace(buf3_p, SmallVec::new());
+ let buf4 = std::mem::replace(buf4_p, SmallVec::new());
+ let buf5 = std::mem::replace(buf5_p, SmallVec::new());
+ let buf6 = SmallVec::new();
+ *state = State::Csi6(buf1, buf2, buf3, buf4, buf5, buf6);
+ }
+ (c, State::Csi6(_, _, _, _, _, ref mut buf)) if (b'0'..=b'9').contains(&c) => {
+ buf.push(c);
+ }
+ (
+ b'm',
+ State::Csi6(
+ ref mut buf1,
+ ref mut buf2,
+ ref mut _color_space_buf,
+ ref mut r_buf,
+ ref mut g_buf,
+ ref mut b_buf,
+ ),
+ ) if buf1.as_ref() == b"38" && buf2.as_ref() == b"2" => {
+ /* Set true foreground color */
+ *fg_color = match (
+ unsafe { std::str::from_utf8_unchecked(r_buf) }.parse::<u8>(),
+ unsafe { std::str::from_utf8_unchecked(g_buf) }.parse::<u8>(),
+ unsafe { std::str::from_utf8_unchecked(b_buf) }.parse::<u8>(),
+ ) {
+ (Ok(r), Ok(g), Ok(b)) => Color::Rgb(r, g, b),
+ _ => Color::Default,
+ };
+ grid[cursor_val!()].set_fg(*fg_color);
+ *state = State::Normal;
+ }
+ (
+ b'm',
+ State::Csi6(
+ ref mut buf1,
+ ref mut buf2,
+ ref mut _color_space_buf,
+ ref mut r_buf,
+ ref mut g_buf,
+ ref mut b_buf,
+ ),
+ ) if buf1.as_ref() == b"48" && buf2.as_ref() == b"2" => {
+ /* Set true background color */
+ *bg_color = match (
+ unsafe { std::str::from_utf8_unchecked(r_buf) }.parse::<u8>(),
+ unsafe { std::str::from_utf8_unchecked(g_buf) }.parse::<u8>(),
+ unsafe { std::str::from_utf8_unchecked(b_buf) }.parse::<u8>(),
+ ) {
+ (Ok(r), Ok(g), Ok(b)) => Color::Rgb(r, g, b),
+ _ => Color::Default,
+ };
+ grid[cursor_val!()].set_bg(*bg_color);
+ *state = State::Normal;
+ }
+ (
+ b'm',
+ State::Csi5(
+ ref mut buf1,
+ ref mut buf2,
+ ref mut r_buf,
+ ref mut g_buf,
+ ref mut b_buf,
+ ),
+ ) if buf1.as_ref() == b"38" && buf2.as_ref() == b"2" => {
+ /* Set true foreground color */
+ *fg_color = match (
+ unsafe { std::str::from_utf8_unchecked(r_buf) }.parse::<u8>(),
+ unsafe { std::str::from_utf8_unchecked(g_buf) }.parse::<u8>(),
+ unsafe { std::str::from_utf8_unchecked(b_buf) }.parse::<u8>(),
+ ) {
+ (Ok(r), Ok(g), Ok(b)) => Color::Rgb(r, g, b),
+ _ => Color::Default,
+ };
+ grid[cursor_val!()].set_fg(*fg_color);
+ *state = State::Normal;
+ }
+ (
+ b'm',
+ State::Csi5(
+ ref mut buf1,
+ ref mut buf2,
+ ref mut r_buf,
+ ref mut g_buf,
+ ref mut b_buf,
+ ),
+ ) if buf1.as_ref() == b"48" && buf2.as_ref() == b"2" => {
+ /* Set true background color */
+ *bg_color = match (
+ unsafe { std::str::from_utf8_unchecked(r_buf) }.parse::<u8>(),
+ unsafe { std::str::from_utf8_unchecked(g_buf) }.parse::<u8>(),
+ unsafe { std::str::from_utf8_unchecked(b_buf) }.parse::<u8>(),
+ ) {
+ (Ok(r), Ok(g), Ok(b)) => Color::Rgb(r, g, b),
+ _ => Color::Default,
+ };
+ grid[cursor_val!()].set_bg(*bg_color);
+ *state = State::Normal;
+ }
+ (b'q', State::Csi1(buf))
+ if buf.len() == 2 && buf[1] == b' ' && (b'0'..=b'6').contains(&buf[0]) =>
+ {
+ /*
+ CSI Ps SP q
+ Set cursor style (DECSCUSR), VT520.
+ Ps = 0 ⇒ blinking block.
+ Ps = 1 ⇒ blinking block (default).
+ Ps = 2 ⇒ steady block.
+ Ps = 3 ⇒ blinking underline.
+ Ps = 4 ⇒ steady underline.
+ Ps = 5 ⇒ blinking bar, xterm.
+ Ps = 6 ⇒ steady bar, xterm.
+ */
+ *state = State::Normal;
+ }
(_, State::Csi) => {
- debug!("ignoring unknown code {}", EscCode::from((&(*state), byte)));
+ debug!(
+ "state: {:?} ignoring unknown code {} byte {}",
+ &state,
+ EscCode::from((&(*state), byte)),
+ byte
+ );
*state = State::Normal;
}
(_, State::Csi1(_)) => {
- debug!("ignoring unknown code {}", EscCode::from((&(*state), byte)));
+ debug!(
+ "state: {:?} ignoring unknown code {} byte {}",
+ &state,
+ EscCode::from((&(*state), byte)),
+ byte
+ );
*state = State::Normal;
}
(_, State::Csi2(_, _)) => {
- debug!("ignoring unknown code {}", EscCode::from((&(*state), byte)));
+ debug!(
+ "state: {:?} ignoring unknown code {} byte {}",
+ &state,
+ EscCode::from((&(*state), byte)),
+ byte
+ );
*state = State::Normal;
}
(_, State::Csi3(_, _, _)) => {
- debug!("ignoring unknown code {}", EscCode::from((&(*state), byte)));
+ debug!(
+ "state: {:?} ignoring unknown code {} byte {}",
+ &state,
+ EscCode::from((&(*state), byte)),
+ byte
+ );
+ *state = State::Normal;
+ }
+ (_, State::Csi4(_, _, _, _)) => {
+ debug!(
+ "state: {:?} ignoring unknown code {} byte {}",
+ &state,
+ EscCode::from((&(*state), byte)),
+ byte
+ );
+ *state = State::Normal;
+ }
+ (_, State::Csi5(_, _, _, _, _)) => {
+ debug!(
+ "state: {:?} ignoring unknown code {} byte {}",
+ &state,
+ EscCode::from((&(*state), byte)),
+ byte
+ );
+ *state = State::Normal;
+ }
+ (_, State::Csi6(_, _, _, _, _, _)) => {
+ debug!(
+ "state: {:?} ignoring unknown code {} byte {}",
+ &state,
+ EscCode::from((&(*state), byte)),
+ byte
+ );
*state = State::Normal;
}
(_, State::Osc1(_)) => {
- debug!("ignoring unknown code {}", EscCode::from((&(*state), byte)));
+ debug!(
+ "state: {:?} ignoring unknown code {} byte {}",
+ &state,
+ EscCode::from((&(*state), byte)),
+ byte
+ );
*state = State::Normal;
}
(_, State::Osc2(_, _)) => {
- debug!("ignoring unknown code {}", EscCode::from((&(*state), byte)));
+ debug!(
+ "state: {:?} ignoring unknown code {} byte {}",
+ &state,
+ EscCode::from((&(*state), byte)),
+ byte
+ );
*state = State::Normal;
}
(_, State::CsiQ(_)) => {
- debug!("ignoring unknown code {}", EscCode::from((&(*state), byte)));
+ debug!(
+ "state: {:?} ignoring unknown code {} byte {}",
+ &state,
+ EscCode::from((&(*state), byte)),
+ byte
+ );
*state = State::Normal;
}
/* other stuff */
diff --git a/src/terminal/position.rs b/src/terminal/position.rs
index 6cdee508..a7cad12b 100644
--- a/src/terminal/position.rs
+++ b/src/terminal/position.rs
@@ -65,35 +65,43 @@ pub type Area = (Pos, Pos);
/// Get an area's height
///
/// Example:
-/// ```
+/// ```no_run
+/// use meli::height;
+///
/// let new_area = ((0, 0), (1, 1));
/// assert_eq!(height!(new_area), 1);
/// ```
#[macro_export]
macro_rules! height {
($a:expr) => {
- (get_y(bottom_right!($a))).saturating_sub(get_y(upper_left!($a)))
+ ($crate::get_y($crate::bottom_right!($a)))
+ .saturating_sub($crate::get_y($crate::upper_left!($a)))
};
}
/// Get an area's width
///
/// Example:
-/// ```
+/// ```no_run
+/// use meli::width;
+///
/// let new_area = ((0, 0), (1, 1));
/// assert_eq!(width!(new_area), 1);
/// ```
#[macro_export]
macro_rules! width {
($a:expr) => {
- (get_x(bottom_right!($a))).saturating_sub(get_x(upper_left!($a)))
+ ($crate::get_x($crate::bottom_right!($a)))
+ .saturating_sub($crate::get_x($crate::upper_left!($a)))
};
}
/// Get the upper left Position of an area
///
/// Example:
-/// ```
+/// ```no_run
+/// use meli::upper_left;
+///
/// let new_area = ((0, 0), (1, 1));
/// assert_eq!(upper_left!(new_area), (0, 0));
/// ```
@@ -107,7 +115,9 @@ macro_rules! upper_left {
/// Get the bottom right Position of an area
///
/// Example:
-/// ```
+/// ```no_run
+/// use meli::bottom_right;
+///
/// let new_area = ((0, 0), (1, 1));
/// assert_eq!(bottom_right!(new_area), (1, 1));
/// ```
@@ -121,7 +131,9 @@ macro_rules! bottom_right {
/// Check if area is valid.
///
/// Example:
-/// ```
+/// ```no_run
+/// use meli::is_valid_area;
+///
/// let valid_area = ((0, 0), (1, 1));
/// assert!(is_valid_area!(valid_area));
///
@@ -132,9 +144,10 @@ macro_rules! bottom_right {
#[macro_export]
macro_rules! is_valid_area {
($a:expr) => {{
- let upper_left = upper_left!($a);
- let bottom_right = bottom_right!($a);
- !(get_y(upper_left) > get_y(bottom_right) || get_x(upper_left) > get_x(bottom_right))
+ let upper_left = $crate::upper_left!($a);
+ let bottom_right = $crate::bottom_right!($a);
+ !($crate::get_y(upper_left) > $crate::get_y(bottom_right)
+ || $crate::get_x(upper_left) > $crate::get_x(bottom_right))
}};
}
diff --git a/tools/Cargo.toml b/tools/Cargo.toml
index e855132f..4c72161e 100644
--- a/tools/Cargo.toml
+++ b/tools/Cargo.toml
@@ -21,11 +21,21 @@ path = "src/imapshell.rs"
name = "smtp_conn"
path = "src/smtp_conn.rs"
+[[bin]]
+name = "embed"
+path = "src/embed.rs"
+
+
[dependencies]
melib = { path = "../melib", version = "*", features = ["debug-tracing", "unicode_algorithms"] }
+meli = { path = "..", version = "*" }
+crossbeam = { version = "^0.8" }
+signal-hook = { version = "^0.3", default-features = false }
+signal-hook-registry = { version = "1.2.0", default-features = false }
+nix = { version = "^0.24", default-features = false }
[features]
-default = []
+default = ["debug-tracing"]
# Print tracing logs as meli runs
-debug-tracing = []
+debug-tracing = ["meli/debug-tracing"]
diff --git a/tools/src/embed.rs b/tools/src/embed.rs
index e9183023..b2a2299a 100644
--- a/tools/src/embed.rs
+++ b/tools/src/embed.rs
@@ -1,7 +1,582 @@
-extern crate ui;
-use ui::terminal::embed::create_pty;
+use meli::terminal::embed::*;
+use meli::terminal::*;
+use meli::*;
+use nix::sys::wait::WaitStatus;
+use std::fs::File;
+use std::io::prelude::*;
+use std::os::raw::c_int;
+use std::sync::{Arc, Mutex};
+
+fn notify(
+ signals: &[c_int],
+ sender: crossbeam::channel::Sender<ThreadEvent>,
+) -> std::result::Result<crossbeam::channel::Receiver<c_int>, std::io::Error> {
+ use std::time::Duration;
+ let (alarm_pipe_r, alarm_pipe_w) =
+ nix::unistd::pipe().map_err(|err| std::io::Error::from_raw_os_error(err as i32))?;
+ let alarm_handler = move |info: &nix::libc::siginfo_t| {
+ let value = unsafe { info.si_value().sival_ptr as u8 };
+ let _ = nix::unistd::write(alarm_pipe_w, &[value]);
+ };
+ unsafe {
+ signal_hook_registry::register_sigaction(signal_hook::consts::SIGALRM, alarm_handler)?;
+ }
+ let (s, r) = crossbeam::channel::bounded(100);
+ let mut signals = signal_hook::iterator::Signals::new(signals)?;
+ let _ = nix::fcntl::fcntl(
+ alarm_pipe_r,
+ nix::fcntl::FcntlArg::F_SETFL(nix::fcntl::OFlag::O_NONBLOCK),
+ );
+ std::thread::spawn(move || {
+ let mut ctr = 0;
+ loop {
+ ctr %= 3;
+ if ctr == 0 {
+ let _ = sender
+ .send_timeout(ThreadEvent::Pulse, Duration::from_millis(500))
+ .ok();
+ }
+
+ for signal in signals.pending() {
+ let _ = s.send_timeout(signal, Duration::from_millis(500)).ok();
+ }
+
+ std::thread::sleep(std::time::Duration::from_millis(100));
+ ctr += 1;
+ }
+ });
+ Ok(r)
+}
+
+#[derive(Debug)]
+enum EmbedStatus {
+ Stopped(Arc<Mutex<EmbedTerminal>>),
+ Running(Arc<Mutex<EmbedTerminal>>),
+}
+
+impl EmbedStatus {
+ #[inline(always)]
+ fn is_stopped(&self) -> bool {
+ matches!(self, Self::Stopped(_))
+ }
+}
+
+impl std::ops::Deref for EmbedStatus {
+ type Target = Arc<Mutex<EmbedTerminal>>;
+ fn deref(&self) -> &Arc<Mutex<EmbedTerminal>> {
+ use EmbedStatus::*;
+ match self {
+ Stopped(ref e) | Running(ref e) => e,
+ }
+ }
+}
+
+impl std::ops::DerefMut for EmbedStatus {
+ fn deref_mut(&mut self) -> &mut Arc<Mutex<EmbedTerminal>> {
+ use EmbedStatus::*;
+ match self {
+ Stopped(ref mut e) | Running(ref mut e) => e,
+ }
+ }
+}
+
+#[derive(Debug)]
+struct EmbedContainer {
+ command: String,
+ embed_area: Area,
+ embed: Option<EmbedStatus>,
+ id: ComponentId,
+ dirty: bool,
+ log_file: File,
+}
+
+impl EmbedContainer {
+ fn new(command: String) -> Box<dyn Component> {
+ Box::new(Self {
+ command,
+ embed: None,
+ embed_area: ((0, 0), (80, 20)),
+ dirty: true,
+ log_file: File::open(".embed.out").unwrap(),
+ id: ComponentId::new_v4(),
+ })
+ }
+}
+
+impl std::fmt::Display for EmbedContainer {
+ fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
+ write!(fmt, "embed")
+ }
+}
+
+impl Component for EmbedContainer {
+ fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
+ if let Some(ref mut embed_pty) = self.embed {
+ let embed_area = area;
+ let theme_default = crate::conf::value(context, "theme_default");
+ match embed_pty {
+ EmbedStatus::Running(_) => {
+ let mut guard = embed_pty.lock().unwrap();
+ clear_area(grid, embed_area, theme_default);
+ copy_area(
+ grid,
+ guard.grid.buffer(),
+ embed_area,
+ ((0, 0), pos_dec(guard.grid.terminal_size, (1, 1))),
+ );
+ guard.set_terminal_size((width!(embed_area), height!(embed_area)));
+ context.dirty_areas.push_back(area);
+ self.dirty = false;
+ return;
+ }
+ EmbedStatus::Stopped(_) => {
+ let guard = embed_pty.lock().unwrap();
+ copy_area(
+ grid,
+ guard.grid.buffer(),
+ embed_area,
+ ((0, 0), pos_dec(guard.grid.terminal_size, (1, 1))),
+ );
+ change_colors(grid, embed_area, Color::Byte(8), theme_default.bg);
+ let stopped_message: String =
+ format!("Process with PID {} has stopped.", guard.child_pid);
+ let stopped_message_2: String = format!("-press 'e' to re-activate.",);
+ const STOPPED_MESSAGE_3: &str =
+ "-press Ctrl-C to forcefully kill it and return to editor.";
+ let max_len = std::cmp::max(
+ stopped_message.len(),
+ std::cmp::max(stopped_message_2.len(), STOPPED_MESSAGE_3.len()),
+ );
+ let inner_area = create_box(
+ grid,
+ (
+ pos_inc(upper_left!(area), (1, 0)),
+ pos_inc(
+ upper_left!(area),
+ (
+ std::cmp::min(max_len + 5, width!(area)),
+ std::cmp::min(5, height!(area)),
+ ),
+ ),
+ ),
+ );
+ clear_area(grid, inner_area, theme_default);
+ for (i, l) in [
+ stopped_message.as_str(),
+ stopped_message_2.as_str(),
+ STOPPED_MESSAGE_3,
+ ]
+ .iter()
+ .enumerate()
+ {
+ write_string_to_grid(
+ l,
+ grid,
+ theme_default.fg,
+ theme_default.bg,
+ theme_default.attrs,
+ (
+ pos_inc((0, i), upper_left!(inner_area)),
+ bottom_right!(inner_area),
+ ),
+ Some(get_x(upper_left!(inner_area))),
+ );
+ }
+ }
+ }
+ } else {
+ let theme_default = crate::conf::value(context, "theme_default");
+ clear_area(grid, area, theme_default);
+ self.embed_area = (upper_left!(area), bottom_right!(area));
+ match create_pty(
+ width!(self.embed_area),
+ height!(self.embed_area),
+ self.command.clone(),
+ ) {
+ Ok(embed) => {
+ //embed.lock().unwrap().set_log_file(self.log_file.take());
+ self.embed = Some(EmbedStatus::Running(embed));
+ self.set_dirty(true);
+ context
+ .replies
+ .push_back(UIEvent::ChangeMode(UIMode::Embed));
+ context.replies.push_back(UIEvent::Fork(ForkType::Embed(
+ self.embed.as_ref().unwrap().lock().unwrap().child_pid,
+ )));
+ }
+ Err(err) => {
+ context.replies.push_back(UIEvent::Notification(
+ Some(format!("Failed to create pseudoterminal: {}", err)),
+ err.to_string(),
+ Some(NotificationType::Error(melib::error::ErrorKind::External)),
+ ));
+ }
+ }
+ }
+ context.dirty_areas.push_back(area);
+ self.dirty = false;
+ return;
+ }
+
+ fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool {
+ match event {
+ UIEvent::EmbedInput((Key::Ctrl('z'), _)) => {
+ self.embed.as_ref().unwrap().lock().unwrap().stop();
+ match self.embed.take() {
+ Some(EmbedStatus::Running(e)) | Some(EmbedStatus::Stopped(e)) => {
+ self.embed = Some(EmbedStatus::Stopped(e));
+ }
+ _ => {}
+ }
+ context
+ .replies
+ .push_back(UIEvent::ChangeMode(UIMode::Normal));
+ self.set_dirty(true);
+ }
+ UIEvent::EmbedInput((ref k, ref b)) => {
+ let _ = self
+ .log_file
+ .write_all(format!("{} bytes {:?}", k, b).as_bytes());
+ let _ = self.log_file.flush();
+ if let Some(ref mut embed) = self.embed {
+ let mut embed_guard = embed.lock().unwrap();
+ if embed_guard.write_all(b).is_err() {
+ match embed_guard.is_active() {
+ Ok(WaitStatus::Exited(_, exit_code)) => {
+ drop(embed_guard);
+ let embed = self.embed.take();
+ if exit_code != 0 {
+ context.replies.push_back(UIEvent::Notification(
+ None,
+ format!(
+ "Subprocess has exited with exit code {}",
+ exit_code
+ ),
+ Some(NotificationType::Error(
+ melib::error::ErrorKind::External,
+ )),
+ ));
+ }
+ self.set_dirty(true);
+ context
+ .replies
+ .push_back(UIEvent::ChangeMode(UIMode::Normal));
+ }
+ #[cfg(any(target_os = "linux", target_os = "android"))]
+ Ok(WaitStatus::PtraceEvent(_, _, _))
+ | Ok(WaitStatus::PtraceSyscall(_)) => {
+ drop(embed_guard);
+ match self.embed.take() {
+ Some(EmbedStatus::Running(e))
+ | Some(EmbedStatus::Stopped(e)) => {
+ self.embed = Some(EmbedStatus::Stopped(e));
+ }
+ _ => {}
+ }
+ context
+ .replies
+ .push_back(UIEvent::ChangeMode(UIMode::Normal));
+ self.set_dirty(true);
+ return true;
+ }
+ Ok(WaitStatus::Stopped(_, _)) => {
+ drop(embed_guard);
+ match self.embed.take() {
+ Some(EmbedStatus::Running(e))
+ | Some(EmbedStatus::Stopped(e)) => {
+ self.embed = Some(EmbedStatus::Stopped(e));
+ }
+ _ => {}
+ }
+ context
+ .replies
+ .push_back(UIEvent::ChangeMode(UIMode::Normal));
+ self.set_dirty(true);
+ return true;
+ }
+ Ok(WaitStatus::Continued(_)) | Ok(WaitStatus::StillAlive) => {
+ context
+ .replies
+ .push_back(UIEvent::EmbedInput((k.clone(), b.to_vec())));
+ return true;
+ }
+ Ok(WaitStatus::Signaled(_, signal, _)) => {
+ drop(embed_guard);
+ context.replies.push_back(UIEvent::Notification(
+ None,
+ format!("Subprocess was killed by {} signal", signal),
+ Some(NotificationType::Error(
+ melib::error::ErrorKind::External,
+ )),
+ ));
+ self.embed = None;
+ context
+ .replies
+ .push_back(UIEvent::ChangeMode(UIMode::Normal));
+ }
+ Err(err) => {
+ context.replies.push_back(UIEvent::Notification(
+ Some("Embed editor crashed.".to_string()),
+ format!("Subprocess has exited with reason {}", &err),
+ Some(NotificationType::Error(
+ melib::error::ErrorKind::External,
+ )),
+ ));
+ drop(embed_guard);
+ self.embed = None;
+ context
+ .replies
+ .push_back(UIEvent::ChangeMode(UIMode::Normal));
+ }
+ }
+ }
+ }
+ self.set_dirty(true);
+ return true;
+ }
+ UIEvent::Resize => {
+ self.set_dirty(true);
+ }
+ UIEvent::Input(Key::Char('e')) if self.embed.is_some() => {
+ self.embed.as_ref().unwrap().lock().unwrap().wake_up();
+ match self.embed.take() {
+ Some(EmbedStatus::Running(e)) | Some(EmbedStatus::Stopped(e)) => {
+ self.embed = Some(EmbedStatus::Running(e));
+ }
+ _ => {}
+ }
+ context
+ .replies
+ .push_back(UIEvent::ChangeMode(UIMode::Embed));
+ self.set_dirty(true);
+ return true;
+ }
+ UIEvent::Input(Key::Ctrl('c'))
+ if self.embed.is_some() && self.embed.as_ref().unwrap().is_stopped() =>
+ {
+ match self.embed.take() {
+ Some(EmbedStatus::Running(embed)) | Some(EmbedStatus::Stopped(embed)) => {
+ let guard = embed.lock().unwrap();
+ guard.wake_up();
+ guard.terminate();
+ }
+ _ => {}
+ }
+ context.replies.push_back(UIEvent::Notification(
+ None,
+ "Subprocess was killed by SIGTERM signal".to_string(),
+ Some(NotificationType::Error(melib::error::ErrorKind::External)),
+ ));
+ context
+ .replies
+ .push_back(UIEvent::ChangeMode(UIMode::Normal));
+ self.set_dirty(true);
+ return true;
+ }
+ _ => {}
+ }
+ false
+ }
+
+ fn is_dirty(&self) -> bool {
+ true
+ }
+
+ fn set_dirty(&mut self, value: bool) {
+ self.dirty = value;
+ }
+
+ fn id(&self) -> ComponentId {
+ self.id
+ }
+}
fn main() -> std::io::Result<()> {
- create_pty().unwrap();
+ let command = std::env::args()
+ .skip(1)
+ .next()
+ .expect("expected command as first argument");
+ /* Create a channel to communicate with other threads. The main process is the sole receiver.
+ * */
+ let (sender, receiver) = crossbeam::channel::bounded(32 * ::std::mem::size_of::<ThreadEvent>());
+ /* Catch SIGWINCH to handle terminal resizing */
+ let signals = &[
+ /* Catch SIGWINCH to handle terminal resizing */
+ signal_hook::consts::SIGWINCH,
+ /* Catch SIGCHLD to handle embed applications status change */
+ signal_hook::consts::SIGCHLD,
+ ];
+ let quit_key: Key = Key::Char('q');
+
+ let window = EmbedContainer::new(command);
+ let signal_recvr = notify(signals, sender.clone())?;
+ let mut state = meli::State::new(Some(Default::default()), sender, receiver.clone()).unwrap();
+ let status_bar = Box::new(StatusBar::new(&state.context, window));
+ state.register_component(status_bar);
+ /* Keep track of the input mode. See UIMode for details */
+ 'main: loop {
+ state.render();
+
+ 'inner: loop {
+ /* Check if any components have sent reply events to State. */
+ let events: smallvec::SmallVec<[UIEvent; 8]> = state.context.replies();
+ for e in events {
+ state.rcv_event(e);
+ }
+ state.redraw();
+
+ /* Poll on all channels. Currently we have the input channel for stdin, watching events and the signal watcher. */
+ crossbeam::select! {
+ recv(receiver) -> r => {
+ match r {
+ Ok(ThreadEvent::Pulse) | Ok(ThreadEvent::UIEvent(UIEvent::Timer(_))) => {},
+ _ => {debug!(&r);}
+ }
+ match r.unwrap() {
+ ThreadEvent::Input((Key::Ctrl('z'), _)) if state.mode != UIMode::Embed => {
+ state.switch_to_main_screen();
+ //_thread_handler.join().expect("Couldn't join on the associated thread");
+ let self_pid = nix::unistd::Pid::this();
+ nix::sys::signal::kill(self_pid, nix::sys::signal::Signal::SIGSTOP).unwrap();
+ state.switch_to_alternate_screen();
+ // BUG: thread sends input event after one received key
+ state.update_size();
+ state.render();
+ state.redraw();
+ },
+ ThreadEvent::Input(raw_input @ (Key::Ctrl('l'), _)) => {
+ /* Manual screen redraw */
+ state.update_size();
+ state.render();
+ state.redraw();
+ if state.mode == UIMode::Embed {
+ state.rcv_event(UIEvent::EmbedInput(raw_input));
+ state.redraw();
+ }
+ },
+ ThreadEvent::Input((k, r)) => {
+ match state.mode {
+ UIMode::Normal => {
+ match k {
+ _ if k == quit_key => {
+ if state.can_quit_cleanly() {
+ drop(state);
+ break 'main;
+ } else {
+ state.redraw();
+ }
+ },
+ key => {
+ state.rcv_event(UIEvent::Input(key));
+ state.redraw();
+ },
+ }
+ },
+ UIMode::Insert => {
+ match k {
+ Key::Esc => {
+ state.rcv_event(UIEvent::ChangeMode(UIMode::Normal));
+ state.redraw();
+ },
+ k => {
+ state.rcv_event(UIEvent::InsertInput(k));
+ state.redraw();
+ },
+ }
+ }
+ UIMode::Command => {
+ match k {
+ Key::Char('\n') => {
+ state.mode = UIMode::Normal;
+ state.rcv_event(UIEvent::ChangeMode(UIMode::Normal));
+ state.redraw();
+ },
+ k => {
+ state.rcv_event(UIEvent::CmdInput(k));
+ state.redraw();
+ },
+ }
+ },
+ UIMode::Embed => {
+ state.rcv_event(UIEvent::EmbedInput((k,r)));
+ state.redraw();
+ },
+ UIMode::Fork => {
+ break 'inner; // `goto` 'reap loop, and wait on child.
+ },
+ }
+ },
+ ThreadEvent::RefreshMailbox(event) => {
+ state.refresh_event(*event);
+ state.redraw();
+ },
+ ThreadEvent::UIEvent(UIEvent::ChangeMode(f)) => {
+ state.mode = f;
+ if f == UIMode::Fork {
+ break 'inner; // `goto` 'reap loop, and wait on child.
+ }
+ }
+ ThreadEvent::UIEvent(e) => {
+ state.rcv_event(e);
+ state.redraw();
+ },
+ ThreadEvent::Pulse => {
+ state.check_accounts();
+ state.redraw();
+ },
+ ThreadEvent::JobFinished(id) => {
+ debug!("Job finished {}", id);
+ for account in state.context.accounts.values_mut() {
+ if account.process_event(&id) {
+ break;
+ }
+ }
+ //state.new_thread(id, name);
+ },
+ }
+ },
+ recv(signal_recvr) -> sig => {
+ match sig.unwrap() {
+ signal_hook::consts::SIGWINCH => {
+ if state.mode != UIMode::Fork {
+ state.update_size();
+ state.render();
+ state.redraw();
+ }
+ },
+ signal_hook::consts::SIGCHLD => {
+ state.rcv_event(UIEvent::EmbedInput((Key::Null, vec![0])));
+ state.redraw();
+
+ }
+ other => {
+ debug!("got other signal: {:?}", other);
+ }
+ }
+ },
+ }
+ } // end of 'inner
+
+ 'reap: loop {
+ match state.try_wait_on_child() {
+ Some(true) => {
+ state.restore_input();
+ state.switch_to_alternate_screen();
+ }
+ Some(false) => {
+ use std::{thread, time};
+ let ten_millis = time::Duration::from_millis(1500);
+ thread::sleep(ten_millis);
+
+ continue 'reap;
+ }
+ None => {
+ state.mode = UIMode::Normal;
+ state.render();
+ break 'reap;
+ }
+ }
+ }
+ }
Ok(())
}