embed test #3
parent
ec35b9fa0a
commit
41c1f67e7a
11
src/bin.rs
11
src/bin.rs
|
@ -197,6 +197,8 @@ fn main() -> std::result::Result<(), std::io::Error> {
|
||||||
let signals = &[
|
let signals = &[
|
||||||
/* Catch SIGWINCH to handle terminal resizing */
|
/* Catch SIGWINCH to handle terminal resizing */
|
||||||
signal_hook::SIGWINCH,
|
signal_hook::SIGWINCH,
|
||||||
|
/* Catch SIGCHLD to handle embed applications status change */
|
||||||
|
signal_hook::SIGCHLD,
|
||||||
];
|
];
|
||||||
|
|
||||||
let signal_recvr = notify(signals, sender)?;
|
let signal_recvr = notify(signals, sender)?;
|
||||||
|
@ -303,15 +305,16 @@ fn main() -> std::result::Result<(), std::io::Error> {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
UIMode::Embed => {
|
UIMode::Embed => {},
|
||||||
state.rcv_event(UIEvent::EmbedInput(k));
|
|
||||||
state.redraw();
|
|
||||||
},
|
|
||||||
UIMode::Fork => {
|
UIMode::Fork => {
|
||||||
break 'inner; // `goto` 'reap loop, and wait on child.
|
break 'inner; // `goto` 'reap loop, and wait on child.
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
ThreadEvent::InputRaw(raw_input) => {
|
||||||
|
state.rcv_event(UIEvent::EmbedInput(raw_input));
|
||||||
|
state.redraw();
|
||||||
|
},
|
||||||
ThreadEvent::RefreshMailbox(event) => {
|
ThreadEvent::RefreshMailbox(event) => {
|
||||||
state.refresh_event(*event);
|
state.refresh_event(*event);
|
||||||
state.redraw();
|
state.redraw();
|
||||||
|
|
|
@ -21,10 +21,12 @@
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
use crate::terminal::embed::EmbedPty;
|
use crate::terminal::embed::EmbedGrid;
|
||||||
use melib::Draft;
|
use melib::Draft;
|
||||||
use mime_apps::query_mime_info;
|
use mime_apps::query_mime_info;
|
||||||
|
use nix::sys::wait::WaitStatus;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
enum Cursor {
|
enum Cursor {
|
||||||
|
@ -33,6 +35,31 @@ enum Cursor {
|
||||||
//Attachments,
|
//Attachments,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum EmbedStatus {
|
||||||
|
Stopped(Arc<Mutex<EmbedGrid>>, File),
|
||||||
|
Running(Arc<Mutex<EmbedGrid>>, File),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Deref for EmbedStatus {
|
||||||
|
type Target = Arc<Mutex<EmbedGrid>>;
|
||||||
|
fn deref(&self) -> &Arc<Mutex<EmbedGrid>> {
|
||||||
|
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<EmbedGrid>> {
|
||||||
|
use EmbedStatus::*;
|
||||||
|
match self {
|
||||||
|
Stopped(ref mut e, _) | Running(ref mut e, _) => e,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Composer {
|
pub struct Composer {
|
||||||
reply_context: Option<((usize, usize), Box<ThreadView>)>, // (folder_index, thread_node_index)
|
reply_context: Option<((usize, usize), Box<ThreadView>)>, // (folder_index, thread_node_index)
|
||||||
|
@ -47,7 +74,7 @@ pub struct Composer {
|
||||||
mode: ViewMode,
|
mode: ViewMode,
|
||||||
|
|
||||||
body_area: Area, // Cache body_area in case we need to replace it with a pseudoterminal
|
body_area: Area, // Cache body_area in case we need to replace it with a pseudoterminal
|
||||||
embed: Option<EmbedPty>,
|
embed: Option<EmbedStatus>,
|
||||||
sign_mail: ToggleFlag,
|
sign_mail: ToggleFlag,
|
||||||
dirty: bool,
|
dirty: bool,
|
||||||
has_changes: bool,
|
has_changes: bool,
|
||||||
|
@ -83,6 +110,7 @@ impl Default for Composer {
|
||||||
enum ViewMode {
|
enum ViewMode {
|
||||||
Discard(Uuid, Selector<char>),
|
Discard(Uuid, Selector<char>),
|
||||||
Edit,
|
Edit,
|
||||||
|
Embed,
|
||||||
SelectRecipients(Selector<Address>),
|
SelectRecipients(Selector<Address>),
|
||||||
ThreadView,
|
ThreadView,
|
||||||
}
|
}
|
||||||
|
@ -103,6 +131,14 @@ impl ViewMode {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_embed(&self) -> bool {
|
||||||
|
if let ViewMode::Embed = self {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Composer {
|
impl fmt::Display for Composer {
|
||||||
|
@ -217,10 +253,9 @@ impl Composer {
|
||||||
fn update_draft(&mut self) {
|
fn update_draft(&mut self) {
|
||||||
let header_values = self.form.values_mut();
|
let header_values = self.form.values_mut();
|
||||||
let draft_header_map = self.draft.headers_mut();
|
let draft_header_map = self.draft.headers_mut();
|
||||||
/* avoid extra allocations by updating values instead of inserting */
|
|
||||||
for (k, v) in draft_header_map.iter_mut() {
|
for (k, v) in draft_header_map.iter_mut() {
|
||||||
if let Some(vn) = header_values.remove(k) {
|
if let Some(ref vn) = header_values.get(k) {
|
||||||
std::mem::swap(v, &mut vn.into_string());
|
*v = vn.as_str().to_string();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -510,8 +545,48 @@ impl Component for Composer {
|
||||||
|
|
||||||
/* Regardless of view mode, do the following */
|
/* Regardless of view mode, do the following */
|
||||||
self.form.draw(grid, header_area, context);
|
self.form.draw(grid, header_area, context);
|
||||||
self.pager.set_dirty();
|
if let Some(ref mut embed_pty) = self.embed {
|
||||||
self.pager.draw(grid, body_area, context);
|
if self.dirty {
|
||||||
|
clear_area(grid, body_area);
|
||||||
|
match embed_pty {
|
||||||
|
EmbedStatus::Running(_, _) => {
|
||||||
|
let mut guard = embed_pty.lock().unwrap();
|
||||||
|
copy_area(
|
||||||
|
grid,
|
||||||
|
&guard.grid,
|
||||||
|
body_area,
|
||||||
|
((0, 0), pos_dec(guard.terminal_size, (1, 1))),
|
||||||
|
);
|
||||||
|
if body_area != self.body_area {
|
||||||
|
guard.set_terminal_size((width!(body_area), height!(body_area)));
|
||||||
|
}
|
||||||
|
context.dirty_areas.push_back(body_area);
|
||||||
|
self.dirty = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
EmbedStatus::Stopped(_, _) => {
|
||||||
|
write_string_to_grid(
|
||||||
|
"process has stopped, press 'e' to re-activate",
|
||||||
|
grid,
|
||||||
|
Color::Default,
|
||||||
|
Color::Default,
|
||||||
|
Attr::Default,
|
||||||
|
body_area,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
context.dirty_areas.push_back(body_area);
|
||||||
|
self.dirty = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.pager.set_dirty();
|
||||||
|
self.pager.draw(grid, body_area, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.body_area = body_area;
|
||||||
|
|
||||||
if self.cursor == Cursor::Body {
|
if self.cursor == Cursor::Body {
|
||||||
change_colors(
|
change_colors(
|
||||||
grid,
|
grid,
|
||||||
|
@ -533,28 +608,9 @@ impl Component for Composer {
|
||||||
Color::Default,
|
Color::Default,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
self.body_area = body_area;
|
|
||||||
if let Some(ref mut embed_pty) = self.embed {
|
|
||||||
let lock = embed_pty.grid.lock().unwrap();
|
|
||||||
copy_area(
|
|
||||||
grid,
|
|
||||||
&lock,
|
|
||||||
area,
|
|
||||||
((0, 0), pos_dec(embed_pty.terminal_size, (1, 1))),
|
|
||||||
);
|
|
||||||
for y in 0..embed_pty.terminal_size.1 {
|
|
||||||
for x in 0..embed_pty.terminal_size.0 {
|
|
||||||
if lock[(x, y)].ch() != ' ' {
|
|
||||||
debug!("coors {:?} char = {}", (x, y), lock[(x, y)].ch());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
context.dirty_areas.push_back(area);
|
|
||||||
debug!("copied grid");
|
|
||||||
}
|
|
||||||
|
|
||||||
match self.mode {
|
match self.mode {
|
||||||
ViewMode::ThreadView | ViewMode::Edit => {}
|
ViewMode::ThreadView | ViewMode::Edit | ViewMode::Embed => {}
|
||||||
ViewMode::SelectRecipients(ref mut s) => {
|
ViewMode::SelectRecipients(ref mut s) => {
|
||||||
s.draw(grid, center_area(area, s.content.size()), context);
|
s.draw(grid, center_area(area, s.content.size()), context);
|
||||||
}
|
}
|
||||||
|
@ -769,31 +825,158 @@ impl Component for Composer {
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
UIEvent::EmbedInput(Key::Char(c)) => {
|
UIEvent::ChildStatusExited(ref pid, ref exit_code)
|
||||||
debug!("got embed input {:?}", event);
|
if self.embed.is_some()
|
||||||
let mut buf: [u8; 4] = [0; 4];
|
&& *pid
|
||||||
let s = c.encode_utf8(&mut buf);
|
== self
|
||||||
|
.embed
|
||||||
|
.as_ref()
|
||||||
|
.map(|e| e.lock().unwrap().child_pid)
|
||||||
|
.unwrap() =>
|
||||||
|
{
|
||||||
|
self.embed = None;
|
||||||
|
self.mode = ViewMode::Edit;
|
||||||
|
context
|
||||||
|
.replies
|
||||||
|
.push_back(UIEvent::ChangeMode(UIMode::Normal));
|
||||||
|
}
|
||||||
|
UIEvent::ChildStatusStopped(ref pid)
|
||||||
|
if self.embed.is_some()
|
||||||
|
&& *pid
|
||||||
|
== self
|
||||||
|
.embed
|
||||||
|
.as_ref()
|
||||||
|
.map(|e| e.lock().unwrap().child_pid)
|
||||||
|
.unwrap() =>
|
||||||
|
{
|
||||||
|
match self.embed.take() {
|
||||||
|
Some(EmbedStatus::Running(e, f)) | Some(EmbedStatus::Stopped(e, f)) => {
|
||||||
|
self.embed = Some(EmbedStatus::Stopped(e, f));
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
context
|
||||||
|
.replies
|
||||||
|
.push_back(UIEvent::ChangeMode(UIMode::Normal));
|
||||||
|
}
|
||||||
|
UIEvent::ChildStatusContinued(ref pid)
|
||||||
|
if self.embed.is_some()
|
||||||
|
&& *pid
|
||||||
|
== self
|
||||||
|
.embed
|
||||||
|
.as_ref()
|
||||||
|
.map(|e| e.lock().unwrap().child_pid)
|
||||||
|
.unwrap() =>
|
||||||
|
{
|
||||||
|
match self.embed.take() {
|
||||||
|
Some(EmbedStatus::Running(e, f)) | Some(EmbedStatus::Stopped(e, f)) => {
|
||||||
|
self.embed = Some(EmbedStatus::Running(e, f));
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
context
|
||||||
|
.replies
|
||||||
|
.push_back(UIEvent::ChangeMode(UIMode::Embed));
|
||||||
|
}
|
||||||
|
|
||||||
|
UIEvent::EmbedInput((Key::Ctrl('z'), _)) => {
|
||||||
|
self.embed.as_ref().unwrap().lock().unwrap().stop();
|
||||||
|
context
|
||||||
|
.replies
|
||||||
|
.push_back(UIEvent::ChangeMode(UIMode::Normal));
|
||||||
|
}
|
||||||
|
UIEvent::EmbedInput((ref k, ref b)) => {
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
self.embed
|
if let Some(ref mut embed) = self.embed {
|
||||||
.as_mut()
|
let mut embed_guard = embed.lock().unwrap();
|
||||||
.unwrap()
|
if embed_guard.stdin.write_all(b).is_err() {
|
||||||
.stdin
|
match embed_guard.is_active() {
|
||||||
.write_all(s.as_bytes())
|
Ok(WaitStatus::Exited(_, exit_code)) => {
|
||||||
.unwrap();
|
drop(embed_guard);
|
||||||
|
if exit_code != 0 {
|
||||||
|
context.replies.push_back(UIEvent::Notification(
|
||||||
|
None,
|
||||||
|
format!(
|
||||||
|
"Subprocess has exited with exit code {}",
|
||||||
|
exit_code
|
||||||
|
),
|
||||||
|
Some(NotificationType::ERROR),
|
||||||
|
));
|
||||||
|
} else if let EmbedStatus::Running(_, f) = embed {
|
||||||
|
let result = f.read_to_string();
|
||||||
|
match Draft::from_str(result.as_str()) {
|
||||||
|
Ok(mut new_draft) => {
|
||||||
|
std::mem::swap(
|
||||||
|
self.draft.attachments_mut(),
|
||||||
|
new_draft.attachments_mut(),
|
||||||
|
);
|
||||||
|
if self.draft != new_draft {
|
||||||
|
self.has_changes = true;
|
||||||
|
}
|
||||||
|
self.draft = new_draft;
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
context.replies.push_back(UIEvent::Notification(
|
||||||
|
None,
|
||||||
|
"Could not parse draft headers correctly. The invalid text has been set as the body of your draft".to_string(),
|
||||||
|
Some(NotificationType::ERROR),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.initialized = false;
|
||||||
|
}
|
||||||
|
self.embed = None;
|
||||||
|
self.mode = ViewMode::Edit;
|
||||||
|
context
|
||||||
|
.replies
|
||||||
|
.push_back(UIEvent::ChangeMode(UIMode::Normal));
|
||||||
|
}
|
||||||
|
Ok(WaitStatus::Stopped(_, _)) => {
|
||||||
|
drop(embed_guard);
|
||||||
|
match self.embed.take() {
|
||||||
|
Some(EmbedStatus::Running(e, f))
|
||||||
|
| Some(EmbedStatus::Stopped(e, f)) => {
|
||||||
|
self.embed = Some(EmbedStatus::Stopped(e, f));
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
self.dirty = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
Ok(WaitStatus::Continued(_)) | Ok(WaitStatus::StillAlive) => {
|
||||||
|
context
|
||||||
|
.replies
|
||||||
|
.push_back(UIEvent::EmbedInput((k.clone(), b.to_vec())));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
e => {
|
||||||
|
context.replies.push_back(UIEvent::Notification(
|
||||||
|
None,
|
||||||
|
format!("Subprocess has exited with reason {:?}", e),
|
||||||
|
Some(NotificationType::ERROR),
|
||||||
|
));
|
||||||
|
drop(embed_guard);
|
||||||
|
self.embed = None;
|
||||||
|
self.mode = ViewMode::Edit;
|
||||||
|
context
|
||||||
|
.replies
|
||||||
|
.push_back(UIEvent::ChangeMode(UIMode::Normal));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
self.dirty = true;
|
self.dirty = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
UIEvent::Input(Key::Char('e')) => {
|
UIEvent::Input(Key::Char('e')) if self.mode.is_embed() => {
|
||||||
/* Edit draft in $EDITOR */
|
self.embed.as_ref().unwrap().lock().unwrap().wake_up();
|
||||||
use std::process::{Command, Stdio};
|
|
||||||
self.embed = Some(crate::terminal::embed::create_pty(self.body_area).unwrap());
|
|
||||||
self.dirty = true;
|
|
||||||
debug!("returned");
|
|
||||||
context
|
context
|
||||||
.replies
|
.replies
|
||||||
.push_back(UIEvent::ChangeMode(UIMode::Embed));
|
.push_back(UIEvent::ChangeMode(UIMode::Embed));
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
UIEvent::Input(Key::Char('e')) => {
|
||||||
|
/* Edit draft in $EDITOR */
|
||||||
let settings = &context.settings;
|
let settings = &context.settings;
|
||||||
let editor = if let Some(editor_cmd) = settings.composing.editor_cmd.as_ref() {
|
let editor = if let Some(editor_cmd) = settings.composing.editor_cmd.as_ref() {
|
||||||
editor_cmd.to_string()
|
editor_cmd.to_string()
|
||||||
|
@ -810,19 +993,37 @@ impl Component for Composer {
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
/* Kill input thread so that spawned command can be sole receiver of stdin */
|
|
||||||
{
|
|
||||||
context.input_kill();
|
|
||||||
}
|
|
||||||
/* update Draft's headers based on form values */
|
/* update Draft's headers based on form values */
|
||||||
self.update_draft();
|
self.update_draft();
|
||||||
let f = create_temp_file(
|
let f = create_temp_file(
|
||||||
self.draft.to_string().unwrap().as_str().as_bytes(),
|
self.draft.to_string().unwrap().as_str().as_bytes(),
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
true,
|
false,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if settings.composing.embed {
|
||||||
|
self.embed = Some(EmbedStatus::Running(
|
||||||
|
crate::terminal::embed::create_pty(
|
||||||
|
self.body_area,
|
||||||
|
[editor, f.path().display().to_string()].join(" "),
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
f,
|
||||||
|
));
|
||||||
|
self.dirty = true;
|
||||||
|
context
|
||||||
|
.replies
|
||||||
|
.push_back(UIEvent::ChangeMode(UIMode::Embed));
|
||||||
|
self.mode = ViewMode::Embed;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
use std::process::{Command, Stdio};
|
||||||
|
/* Kill input thread so that spawned command can be sole receiver of stdin */
|
||||||
|
{
|
||||||
|
context.input_kill();
|
||||||
|
}
|
||||||
|
|
||||||
let parts = split_command!(editor);
|
let parts = split_command!(editor);
|
||||||
let (cmd, args) = (parts[0], &parts[1..]);
|
let (cmd, args) = (parts[0], &parts[1..]);
|
||||||
if let Err(e) = Command::new(cmd)
|
if let Err(e) = Command::new(cmd)
|
||||||
|
|
|
@ -57,6 +57,13 @@ impl Field {
|
||||||
self.as_str().is_empty()
|
self.as_str().is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn to_string(&self) -> String {
|
||||||
|
match self {
|
||||||
|
Text(ref s, _) => s.as_str().to_string(),
|
||||||
|
Choice(ref v, ref cursor) => v[*cursor].clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn into_string(self) -> String {
|
pub fn into_string(self) -> String {
|
||||||
match self {
|
match self {
|
||||||
Text(s, _) => s.into_string(),
|
Text(s, _) => s.into_string(),
|
||||||
|
|
|
@ -18,13 +18,28 @@
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
use super::default_vals::{none, true_val};
|
||||||
|
|
||||||
/// Settings for writing and sending new e-mail
|
/// Settings for writing and sending new e-mail
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct ComposingSettings {
|
pub struct ComposingSettings {
|
||||||
/// A command to pipe new emails to
|
/// A command to pipe new emails to
|
||||||
/// Required
|
/// Required
|
||||||
pub mailer_cmd: String,
|
pub mailer_cmd: String,
|
||||||
/// Command to launch editor. Can have arguments. Draft filename is given as the last argument. If it's missing, the environment variable $EDITOR is looked up.
|
/// Command to launch editor. Can have arguments. Draft filename is given as the last argument. If it's missing, the environment variable $EDITOR is looked up.
|
||||||
|
#[serde(default = "none")]
|
||||||
pub editor_cmd: Option<String>,
|
pub editor_cmd: Option<String>,
|
||||||
|
/// Embed editor (for terminal interfaces) instead of forking and waiting.
|
||||||
|
#[serde(default = "true_val")]
|
||||||
|
pub embed: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ComposingSettings {
|
||||||
|
fn default() -> Self {
|
||||||
|
ComposingSettings {
|
||||||
|
mailer_cmd: String::new(),
|
||||||
|
editor_cmd: None,
|
||||||
|
embed: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,33 +44,39 @@ use termion::{clear, cursor};
|
||||||
pub type StateStdout = termion::screen::AlternateScreen<termion::raw::RawTerminal<std::io::Stdout>>;
|
pub type StateStdout = termion::screen::AlternateScreen<termion::raw::RawTerminal<std::io::Stdout>>;
|
||||||
|
|
||||||
struct InputHandler {
|
struct InputHandler {
|
||||||
rx: Receiver<bool>,
|
rx: Receiver<InputCommand>,
|
||||||
tx: Sender<bool>,
|
tx: Sender<InputCommand>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InputHandler {
|
impl InputHandler {
|
||||||
fn restore(&self, tx: Sender<ThreadEvent>) {
|
fn restore(&self, tx: Sender<ThreadEvent>) {
|
||||||
let stdin = std::io::stdin();
|
|
||||||
let rx = self.rx.clone();
|
let rx = self.rx.clone();
|
||||||
thread::Builder::new()
|
thread::Builder::new()
|
||||||
.name("input-thread".to_string())
|
.name("input-thread".to_string())
|
||||||
.spawn(move || {
|
.spawn(move || {
|
||||||
get_events(
|
get_events(
|
||||||
stdin,
|
|
||||||
|k| {
|
|k| {
|
||||||
tx.send(ThreadEvent::Input(k)).unwrap();
|
tx.send(ThreadEvent::Input(k)).unwrap();
|
||||||
},
|
},
|
||||||
|| {
|
|i| {
|
||||||
tx.send(ThreadEvent::UIEvent(UIEvent::ChangeMode(UIMode::Fork)))
|
tx.send(ThreadEvent::InputRaw(i)).unwrap();
|
||||||
.unwrap();
|
|
||||||
},
|
},
|
||||||
&rx,
|
&rx,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn kill(&self) {
|
fn kill(&self) {
|
||||||
self.tx.send(false).unwrap();
|
self.tx.send(InputCommand::Kill).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn switch_to_raw(&self) {
|
||||||
|
self.tx.send(InputCommand::Raw).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn switch_from_raw(&self) {
|
||||||
|
self.tx.send(InputCommand::NoRaw).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,9 +104,19 @@ impl Context {
|
||||||
pub fn replies(&mut self) -> Vec<UIEvent> {
|
pub fn replies(&mut self) -> Vec<UIEvent> {
|
||||||
self.replies.drain(0..).collect()
|
self.replies.drain(0..).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn input_kill(&self) {
|
pub fn input_kill(&self) {
|
||||||
self.input.kill();
|
self.input.kill();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn input_from_raw(&self) {
|
||||||
|
self.input.switch_from_raw();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn input_to_raw(&self) {
|
||||||
|
self.input.switch_to_raw();
|
||||||
|
}
|
||||||
|
|
||||||
pub fn restore_input(&self) {
|
pub fn restore_input(&self) {
|
||||||
self.input.restore(self.sender.clone());
|
self.input.restore(self.sender.clone());
|
||||||
}
|
}
|
||||||
|
@ -584,10 +600,16 @@ impl State {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
UIEvent::ChangeMode(m) => {
|
UIEvent::ChangeMode(m) => {
|
||||||
|
if self.mode == UIMode::Embed {
|
||||||
|
self.context.input_from_raw();
|
||||||
|
}
|
||||||
self.context
|
self.context
|
||||||
.sender
|
.sender
|
||||||
.send(ThreadEvent::UIEvent(UIEvent::ChangeMode(m)))
|
.send(ThreadEvent::UIEvent(UIEvent::ChangeMode(m)))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
if m == UIMode::Embed {
|
||||||
|
self.context.input_to_raw();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,27 @@
|
||||||
|
use crate::split_command;
|
||||||
use crate::terminal::position::Area;
|
use crate::terminal::position::Area;
|
||||||
|
use crate::terminal::position::*;
|
||||||
|
use melib::log;
|
||||||
|
use melib::ERROR;
|
||||||
|
|
||||||
use nix::fcntl::{open, OFlag};
|
use nix::fcntl::{open, OFlag};
|
||||||
use nix::ioctl_write_ptr_bad;
|
use nix::ioctl_write_ptr_bad;
|
||||||
use nix::libc::{STDERR_FILENO, STDIN_FILENO, STDOUT_FILENO};
|
use nix::libc::{STDERR_FILENO, STDIN_FILENO, STDOUT_FILENO};
|
||||||
use nix::pty::{grantpt, posix_openpt, ptsname, unlockpt, Winsize};
|
use nix::pty::{grantpt, posix_openpt, ptsname, unlockpt, Winsize};
|
||||||
use nix::sys::stat::Mode;
|
use nix::sys::stat::Mode;
|
||||||
use nix::sys::{stat, wait};
|
use nix::sys::{
|
||||||
use nix::unistd::{dup2, fork, setsid, ForkResult};
|
stat,
|
||||||
|
wait::{self, waitpid},
|
||||||
|
};
|
||||||
|
use nix::unistd::{dup2, fork, setsid, ForkResult, Pid};
|
||||||
|
use std::ffi::CString;
|
||||||
use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd};
|
use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd};
|
||||||
|
|
||||||
mod grid;
|
mod grid;
|
||||||
|
|
||||||
use crate::terminal::cells::{Cell, CellBuffer};
|
use crate::terminal::cells::{Cell, CellBuffer};
|
||||||
pub use grid::*;
|
pub use grid::EmbedGrid;
|
||||||
|
use grid::*;
|
||||||
|
|
||||||
// ioctl command to set window size of pty:
|
// ioctl command to set window size of pty:
|
||||||
use libc::TIOCSWINSZ;
|
use libc::TIOCSWINSZ;
|
||||||
|
@ -26,14 +36,7 @@ ioctl_write_ptr_bad!(set_window_size, TIOCSWINSZ, Winsize);
|
||||||
|
|
||||||
static SWITCHALTERNATIVE_1049: &'static [u8] = &[b'1', b'0', b'4', b'9'];
|
static SWITCHALTERNATIVE_1049: &'static [u8] = &[b'1', b'0', b'4', b'9'];
|
||||||
|
|
||||||
#[derive(Debug)]
|
pub fn create_pty(area: Area, command: String) -> nix::Result<Arc<Mutex<EmbedGrid>>> {
|
||||||
pub struct EmbedPty {
|
|
||||||
pub grid: Arc<Mutex<CellBuffer>>,
|
|
||||||
pub stdin: std::fs::File,
|
|
||||||
pub terminal_size: (usize, usize),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_pty(area: Area) -> nix::Result<EmbedPty> {
|
|
||||||
// Open a new PTY master
|
// Open a new PTY master
|
||||||
let master_fd = posix_openpt(OFlag::O_RDWR)?;
|
let master_fd = posix_openpt(OFlag::O_RDWR)?;
|
||||||
|
|
||||||
|
@ -48,7 +51,7 @@ pub fn create_pty(area: Area) -> nix::Result<EmbedPty> {
|
||||||
//let _slave_fd = open(Path::new(&slave_name), OFlag::O_RDWR, Mode::empty())?;
|
//let _slave_fd = open(Path::new(&slave_name), OFlag::O_RDWR, Mode::empty())?;
|
||||||
{
|
{
|
||||||
let winsize = Winsize {
|
let winsize = Winsize {
|
||||||
ws_row: 40,
|
ws_row: 20,
|
||||||
ws_col: 80,
|
ws_col: 80,
|
||||||
ws_xpixel: 0,
|
ws_xpixel: 0,
|
||||||
ws_ypixel: 0,
|
ws_ypixel: 0,
|
||||||
|
@ -57,39 +60,66 @@ pub fn create_pty(area: Area) -> nix::Result<EmbedPty> {
|
||||||
let master_fd = master_fd.clone().into_raw_fd();
|
let master_fd = master_fd.clone().into_raw_fd();
|
||||||
unsafe { set_window_size(master_fd, &winsize).unwrap() };
|
unsafe { set_window_size(master_fd, &winsize).unwrap() };
|
||||||
}
|
}
|
||||||
match fork() {
|
|
||||||
|
let child_pid = match fork() {
|
||||||
Ok(ForkResult::Child) => {
|
Ok(ForkResult::Child) => {
|
||||||
setsid().unwrap(); // create new session with child as session leader
|
/* Open slave end for pseudoterminal */
|
||||||
let slave_fd = open(Path::new(&slave_name), OFlag::O_RDWR, stat::Mode::empty())?;
|
let slave_fd = open(Path::new(&slave_name), OFlag::O_RDWR, stat::Mode::empty())?;
|
||||||
|
|
||||||
// assign stdin, stdout, stderr to the tty, just like a terminal does
|
let child_pid = match fork() {
|
||||||
dup2(slave_fd, STDIN_FILENO).unwrap();
|
Ok(ForkResult::Child) => {
|
||||||
dup2(slave_fd, STDOUT_FILENO).unwrap();
|
// assign stdin, stdout, stderr to the tty, just like a terminal does
|
||||||
dup2(slave_fd, STDERR_FILENO).unwrap();
|
dup2(slave_fd, STDIN_FILENO).unwrap();
|
||||||
std::process::Command::new("vim").status().unwrap();
|
dup2(slave_fd, STDOUT_FILENO).unwrap();
|
||||||
|
dup2(slave_fd, STDERR_FILENO).unwrap();
|
||||||
|
let parts = split_command!(command);
|
||||||
|
let (cmd, _) = (parts[0], &parts[1..]);
|
||||||
|
if let Err(e) = nix::unistd::execv(
|
||||||
|
&CString::new(cmd).unwrap(),
|
||||||
|
&parts
|
||||||
|
.iter()
|
||||||
|
.map(|&a| CString::new(a).unwrap())
|
||||||
|
.collect::<Vec<CString>>(),
|
||||||
|
) {
|
||||||
|
log(format!("Could not execute `{}`: {}", command, e,), ERROR);
|
||||||
|
std::process::exit(-1);
|
||||||
|
}
|
||||||
|
/* This path shouldn't be executed. */
|
||||||
|
std::process::exit(0);
|
||||||
|
}
|
||||||
|
Ok(ForkResult::Parent { child }) => child,
|
||||||
|
Err(e) => panic!(e),
|
||||||
|
};
|
||||||
|
waitpid(child_pid, None).unwrap();
|
||||||
|
std::process::exit(0);
|
||||||
}
|
}
|
||||||
Ok(ForkResult::Parent { child: _ }) => {}
|
Ok(ForkResult::Parent { child }) => child,
|
||||||
Err(e) => panic!(e),
|
Err(e) => panic!(e),
|
||||||
};
|
};
|
||||||
|
|
||||||
let stdin = unsafe { std::fs::File::from_raw_fd(master_fd.clone().into_raw_fd()) };
|
let stdin = unsafe { std::fs::File::from_raw_fd(master_fd.clone().into_raw_fd()) };
|
||||||
let stdin_ = unsafe { std::fs::File::from_raw_fd(master_fd.clone().into_raw_fd()) };
|
let mut embed_grid = EmbedGrid::new(stdin, child_pid);
|
||||||
let grid = Arc::new(Mutex::new(CellBuffer::new(80, 40, Cell::default())));
|
embed_grid.set_terminal_size((width!(area), height!(area)));
|
||||||
|
let grid = Arc::new(Mutex::new(embed_grid));
|
||||||
let grid_ = grid.clone();
|
let grid_ = grid.clone();
|
||||||
let terminal_size = (80, 40);
|
|
||||||
|
|
||||||
std::thread::Builder::new()
|
std::thread::Builder::new()
|
||||||
.spawn(move || {
|
.spawn(move || {
|
||||||
let master_fd = master_fd.into_raw_fd();
|
let master_fd = master_fd.into_raw_fd();
|
||||||
let master_file = unsafe { std::fs::File::from_raw_fd(master_fd) };
|
let master_file = unsafe { std::fs::File::from_raw_fd(master_fd) };
|
||||||
forward_pty_translate_escape_codes(master_file, area, grid_, stdin_);
|
forward_pty_translate_escape_codes(master_file, grid_);
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
Ok(EmbedPty {
|
Ok(grid)
|
||||||
grid,
|
}
|
||||||
stdin,
|
|
||||||
terminal_size,
|
fn forward_pty_translate_escape_codes(pty_fd: std::fs::File, grid: Arc<Mutex<EmbedGrid>>) {
|
||||||
})
|
let mut bytes_iter = pty_fd.bytes();
|
||||||
|
debug!("waiting for bytes");
|
||||||
|
while let Some(Ok(byte)) = bytes_iter.next() {
|
||||||
|
debug!("got byte {}", byte as char);
|
||||||
|
grid.lock().unwrap().process_byte(byte);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -131,6 +161,7 @@ impl std::fmt::Display for EscCode<'_> {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
match self {
|
match self {
|
||||||
|
EscCode(G0, b'B') => write!(f, "ESC(B\t\tG0 USASCII charset set"),
|
||||||
EscCode(G0, c) => write!(f, "ESC({}\t\tG0 charset set", *c as char),
|
EscCode(G0, c) => write!(f, "ESC({}\t\tG0 charset set", *c as char),
|
||||||
EscCode(Osc1(ref buf), ref c) => {
|
EscCode(Osc1(ref buf), ref c) => {
|
||||||
write!(f, "ESC]{}{}\t\tOSC", unsafestr!(buf), *c as char)
|
write!(f, "ESC]{}{}\t\tOSC", unsafestr!(buf), *c as char)
|
||||||
|
@ -148,13 +179,13 @@ impl std::fmt::Display for EscCode<'_> {
|
||||||
),
|
),
|
||||||
EscCode(Csi, b'K') => write!(
|
EscCode(Csi, b'K') => write!(
|
||||||
f,
|
f,
|
||||||
"ESC[K\t\tCSI Erase from the cursor to the end of the line [BAD]"
|
"ESC[K\t\tCSI Erase from the cursor to the end of the line"
|
||||||
),
|
),
|
||||||
EscCode(Csi, b'J') => write!(
|
EscCode(Csi, b'J') => write!(
|
||||||
f,
|
f,
|
||||||
"ESC[J\t\tCSI Erase from the cursor to the end of the screen [BAD]"
|
"ESC[J\t\tCSI Erase from the cursor to the end of the screen"
|
||||||
),
|
),
|
||||||
EscCode(Csi, b'H') => write!(f, "ESC[H\t\tCSI Move the cursor to home position. [BAD]"),
|
EscCode(Csi, b'H') => write!(f, "ESC[H\t\tCSI Move the cursor to home position."),
|
||||||
EscCode(Csi, c) => write!(f, "ESC[{}\t\tCSI [UNKNOWN]", *c as char),
|
EscCode(Csi, c) => write!(f, "ESC[{}\t\tCSI [UNKNOWN]", *c as char),
|
||||||
EscCode(Csi1(ref buf), b'm') => write!(
|
EscCode(Csi1(ref buf), b'm') => write!(
|
||||||
f,
|
f,
|
||||||
|
@ -205,9 +236,31 @@ impl std::fmt::Display for EscCode<'_> {
|
||||||
"ESC[{buf}G\t\tCursor Character Absolute [column={buf}] (default = [row,1])",
|
"ESC[{buf}G\t\tCursor Character Absolute [column={buf}] (default = [row,1])",
|
||||||
buf = unsafestr!(buf)
|
buf = unsafestr!(buf)
|
||||||
),
|
),
|
||||||
|
|
||||||
|
EscCode(Csi1(ref buf), b'P') => write!(
|
||||||
|
f,
|
||||||
|
"ESC[{buf}P\t\tDelete P s Character(s) (default = 1) (DCH). ",
|
||||||
|
buf = unsafestr!(buf)
|
||||||
|
),
|
||||||
|
EscCode(Csi1(ref buf), b'S') => write!(
|
||||||
|
f,
|
||||||
|
"ESC[{buf}S\t\tCSI P s S Scroll up P s lines (default = 1) (SU), VT420, EC",
|
||||||
|
buf = unsafestr!(buf)
|
||||||
|
),
|
||||||
|
EscCode(Csi1(ref buf), b'J') => write!(
|
||||||
|
f,
|
||||||
|
"Erase in display {buf}",
|
||||||
|
buf = unsafestr!(buf)
|
||||||
|
),
|
||||||
EscCode(Csi1(ref buf), c) => {
|
EscCode(Csi1(ref buf), c) => {
|
||||||
write!(f, "ESC[{}{}\t\tCSI [UNKNOWN]", unsafestr!(buf), *c as char)
|
write!(f, "ESC[{}{}\t\tCSI [UNKNOWN]", unsafestr!(buf), *c as char)
|
||||||
}
|
}
|
||||||
|
EscCode(Csi2(ref buf1, ref buf2), b'r') => write!(
|
||||||
|
f,
|
||||||
|
"ESC[{};{}r\t\tCSI Set Scrolling Region [top;bottom] (default = full size of window) (DECSTBM), VT100.",
|
||||||
|
unsafestr!(buf1),
|
||||||
|
unsafestr!(buf2),
|
||||||
|
),
|
||||||
EscCode(Csi2(ref buf1, ref buf2), c) => write!(
|
EscCode(Csi2(ref buf1, ref buf2), c) => write!(
|
||||||
f,
|
f,
|
||||||
"ESC[{};{}{}\t\tCSI",
|
"ESC[{};{}{}\t\tCSI",
|
||||||
|
@ -240,16 +293,24 @@ impl std::fmt::Display for EscCode<'_> {
|
||||||
"ESC[?{}r\t\tCSI Restore DEC Private Mode Values",
|
"ESC[?{}r\t\tCSI Restore DEC Private Mode Values",
|
||||||
unsafestr!(buf)
|
unsafestr!(buf)
|
||||||
),
|
),
|
||||||
EscCode(CsiQ(ref buf), b'h') if buf == &[b'2', b'5'] => write!(
|
EscCode(CsiQ(ref buf), b'h') if buf == b"25" => write!(
|
||||||
f,
|
f,
|
||||||
"ESC[?25h\t\tCSI DEC Private Mode Set (DECSET) show cursor",
|
"ESC[?25h\t\tCSI DEC Private Mode Set (DECSET) show cursor",
|
||||||
),
|
),
|
||||||
|
EscCode(CsiQ(ref buf), b'h') if buf == b"12" => write!(
|
||||||
|
f,
|
||||||
|
"ESC[?12h\t\tCSI DEC Private Mode Set (DECSET) Start Blinking Cursor.",
|
||||||
|
),
|
||||||
EscCode(CsiQ(ref buf), b'h') => write!(
|
EscCode(CsiQ(ref buf), b'h') => write!(
|
||||||
f,
|
f,
|
||||||
"ESC[?{}h\t\tCSI DEC Private Mode Set (DECSET). [UNKNOWN]",
|
"ESC[?{}h\t\tCSI DEC Private Mode Set (DECSET). [UNKNOWN]",
|
||||||
unsafestr!(buf)
|
unsafestr!(buf)
|
||||||
),
|
),
|
||||||
EscCode(CsiQ(ref buf), b'l') if buf == &[b'2', b'5'] => write!(
|
EscCode(CsiQ(ref buf), b'l') if buf == b"12" => write!(
|
||||||
|
f,
|
||||||
|
"ESC[?12l\t\tCSI DEC Private Mode Set (DECSET) Stop Blinking Cursor",
|
||||||
|
),
|
||||||
|
EscCode(CsiQ(ref buf), b'l') if buf == b"25" => write!(
|
||||||
f,
|
f,
|
||||||
"ESC[?25l\t\tCSI DEC Private Mode Set (DECSET) hide cursor",
|
"ESC[?25l\t\tCSI DEC Private Mode Set (DECSET) hide cursor",
|
||||||
),
|
),
|
||||||
|
@ -260,38 +321,3 @@ impl std::fmt::Display for EscCode<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn forward_pty_translate_escape_codes(
|
|
||||||
pty_fd: std::fs::File,
|
|
||||||
area: Area,
|
|
||||||
grid: Arc<Mutex<CellBuffer>>,
|
|
||||||
stdin: std::fs::File,
|
|
||||||
) {
|
|
||||||
let (upper_left, bottom_right) = area;
|
|
||||||
let (upper_x, upper_y) = upper_left;
|
|
||||||
let (bottom_x, bottom_y) = bottom_right;
|
|
||||||
let upper_x_str = upper_x.to_string();
|
|
||||||
let upper_y_str = upper_y.to_string();
|
|
||||||
let bottom_x_str = bottom_x.to_string();
|
|
||||||
let bottom_y_str = bottom_y.to_string();
|
|
||||||
|
|
||||||
debug!(area);
|
|
||||||
debug!(&upper_x_str);
|
|
||||||
debug!(&upper_y_str);
|
|
||||||
debug!(&bottom_x_str);
|
|
||||||
debug!(&bottom_y_str);
|
|
||||||
let mut embed_grid = EmbedGrid::new(grid, stdin);
|
|
||||||
embed_grid.set_terminal_size((79, 39));
|
|
||||||
let mut bytes_iter = pty_fd.bytes();
|
|
||||||
let mut prev_char = b'\0';
|
|
||||||
debug!("waiting for bytes");
|
|
||||||
while let Some(Ok(byte)) = bytes_iter.next() {
|
|
||||||
debug!("got byte {}", byte as char);
|
|
||||||
debug!(
|
|
||||||
"{}{} byte is {} and state is {:?}",
|
|
||||||
prev_char as char, byte as char, byte as char, &embed_grid.state
|
|
||||||
);
|
|
||||||
prev_char = byte;
|
|
||||||
embed_grid.process_byte(byte);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,28 +1,54 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::terminal::cells::{Cell, CellBuffer};
|
use crate::terminal::cells::*;
|
||||||
|
use melib::error::{MeliError, Result};
|
||||||
|
use nix::sys::wait::WaitStatus;
|
||||||
|
use nix::sys::wait::{waitpid, WaitPidFlag};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct EmbedGrid {
|
pub struct EmbedGrid {
|
||||||
cursor: (usize, usize),
|
cursor: (usize, usize),
|
||||||
terminal_size: (usize, usize),
|
pub grid: CellBuffer,
|
||||||
grid: Arc<Mutex<CellBuffer>>,
|
|
||||||
pub state: State,
|
pub state: State,
|
||||||
stdin: std::fs::File,
|
pub stdin: std::fs::File,
|
||||||
|
pub child_pid: nix::unistd::Pid,
|
||||||
|
pub terminal_size: (usize, usize),
|
||||||
|
resized: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EmbedGrid {
|
impl EmbedGrid {
|
||||||
pub fn new(grid: Arc<Mutex<CellBuffer>>, stdin: std::fs::File) -> Self {
|
pub fn new(stdin: std::fs::File, child_pid: nix::unistd::Pid) -> Self {
|
||||||
EmbedGrid {
|
EmbedGrid {
|
||||||
cursor: (1, 1),
|
cursor: (0, 0),
|
||||||
terminal_size: (0, 0),
|
terminal_size: (0, 0),
|
||||||
grid,
|
grid: CellBuffer::default(),
|
||||||
state: State::Normal,
|
state: State::Normal,
|
||||||
stdin,
|
stdin,
|
||||||
|
child_pid,
|
||||||
|
resized: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_terminal_size(&mut self, new_val: (usize, usize)) {
|
pub fn set_terminal_size(&mut self, new_val: (usize, usize)) {
|
||||||
self.terminal_size = new_val;
|
self.terminal_size = new_val;
|
||||||
|
self.grid.resize(new_val.0, new_val.1, Cell::default());
|
||||||
|
self.cursor = (0, 0);
|
||||||
|
nix::sys::signal::kill(self.child_pid, nix::sys::signal::SIGWINCH).unwrap();
|
||||||
|
self.resized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn wake_up(&self) {
|
||||||
|
nix::sys::signal::kill(self.child_pid, nix::sys::signal::SIGCONT).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stop(&self) {
|
||||||
|
debug!("stopping");
|
||||||
|
nix::sys::signal::kill(debug!(self.child_pid), nix::sys::signal::SIGSTOP).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_active(&self) -> Result<WaitStatus> {
|
||||||
|
debug!(waitpid(self.child_pid, Some(WaitPidFlag::WNOHANG),))
|
||||||
|
.map_err(|e| MeliError::new(e.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process_byte(&mut self, byte: u8) {
|
pub fn process_byte(&mut self, byte: u8) {
|
||||||
|
@ -32,15 +58,17 @@ impl EmbedGrid {
|
||||||
ref mut grid,
|
ref mut grid,
|
||||||
ref mut state,
|
ref mut state,
|
||||||
ref mut stdin,
|
ref mut stdin,
|
||||||
|
ref mut resized,
|
||||||
|
child_pid: _,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
macro_rules! increase_cursor_x {
|
macro_rules! increase_cursor_x {
|
||||||
() => {
|
() => {
|
||||||
if *cursor == *terminal_size {
|
if *cursor == *terminal_size {
|
||||||
/* do nothing */
|
/* do nothing */
|
||||||
} else if cursor.0 == terminal_size.0 {
|
} else if cursor.0 + 1 >= terminal_size.0 {
|
||||||
cursor.0 = 0;
|
//cursor.0 = 0;
|
||||||
cursor.1 += 1;
|
//cursor.1 += 1;
|
||||||
} else {
|
} else {
|
||||||
cursor.0 += 1;
|
cursor.0 += 1;
|
||||||
}
|
}
|
||||||
|
@ -62,6 +90,25 @@ impl EmbedGrid {
|
||||||
(b'(', State::ExpectingControlChar) => {
|
(b'(', State::ExpectingControlChar) => {
|
||||||
*state = State::G0;
|
*state = State::G0;
|
||||||
}
|
}
|
||||||
|
(b'J', State::ExpectingControlChar) => {
|
||||||
|
// "ESCJ Erase from the cursor to the end of the screen"
|
||||||
|
debug!("sending {}", EscCode::from((&(*state), byte)));
|
||||||
|
debug!("erasing from {:?} to {:?}", cursor, terminal_size);
|
||||||
|
for y in cursor.1..terminal_size.1 {
|
||||||
|
for x in cursor.0..terminal_size.0 {
|
||||||
|
grid[(x, y)] = Cell::default();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*state = State::Normal;
|
||||||
|
}
|
||||||
|
(b'K', State::ExpectingControlChar) => {
|
||||||
|
// "ESCK Erase from the cursor to the end of the line"
|
||||||
|
debug!("sending {}", EscCode::from((&(*state), byte)));
|
||||||
|
for x in cursor.0..terminal_size.0 {
|
||||||
|
grid[(x, cursor.1)] = Cell::default();
|
||||||
|
}
|
||||||
|
*state = State::Normal;
|
||||||
|
}
|
||||||
(c, State::ExpectingControlChar) => {
|
(c, State::ExpectingControlChar) => {
|
||||||
debug!(
|
debug!(
|
||||||
"unrecognised: byte is {} and state is {:?}",
|
"unrecognised: byte is {} and state is {:?}",
|
||||||
|
@ -89,11 +136,11 @@ impl EmbedGrid {
|
||||||
buf.push(c);
|
buf.push(c);
|
||||||
}
|
}
|
||||||
(c, State::Osc1(_)) => {
|
(c, State::Osc1(_)) => {
|
||||||
debug!("sending {}", EscCode::from((&(*state), byte)));
|
debug!("ignoring {}", EscCode::from((&(*state), byte)));
|
||||||
*state = State::Normal;
|
*state = State::Normal;
|
||||||
}
|
}
|
||||||
(c, State::Osc2(_, _)) => {
|
(c, State::Osc2(_, _)) => {
|
||||||
debug!("sending {}", EscCode::from((&(*state), byte)));
|
debug!("ignoring {}", EscCode::from((&(*state), byte)));
|
||||||
*state = State::Normal;
|
*state = State::Normal;
|
||||||
}
|
}
|
||||||
/* END OF OSC */
|
/* END OF OSC */
|
||||||
|
@ -101,30 +148,69 @@ impl EmbedGrid {
|
||||||
/* ********** */
|
/* ********** */
|
||||||
/* ********** */
|
/* ********** */
|
||||||
/* ********** */
|
/* ********** */
|
||||||
|
(b'\r', State::Normal) => {
|
||||||
|
//debug!("setting cell {:?} char '{}'", cursor, c as char);
|
||||||
|
debug!("carriage return x-> 0, cursor was: {:?}", cursor);
|
||||||
|
cursor.0 = 0;
|
||||||
|
debug!("cursor became: {:?}", cursor);
|
||||||
|
}
|
||||||
|
(b'\n', State::Normal) => {
|
||||||
|
//debug!("setting cell {:?} char '{}'", cursor, c as char);
|
||||||
|
debug!("newline y-> y+1, cursor was: {:?}", cursor);
|
||||||
|
if cursor.1 + 1 < terminal_size.1 {
|
||||||
|
cursor.1 += 1;
|
||||||
|
}
|
||||||
|
debug!("cursor became: {:?}", cursor);
|
||||||
|
}
|
||||||
|
(b'', State::Normal) => {
|
||||||
|
debug!("Visual bell ^G, ignoring {:?}", cursor);
|
||||||
|
}
|
||||||
|
/* Backspace */
|
||||||
|
(0x08, State::Normal) => {
|
||||||
|
//debug!("setting cell {:?} char '{}'", cursor, c as char);
|
||||||
|
debug!("backspace x-> x-1, cursor was: {:?}", cursor);
|
||||||
|
if cursor.0 > 0 {
|
||||||
|
cursor.0 -= 1;
|
||||||
|
}
|
||||||
|
//grid[*cursor].set_ch(' ');
|
||||||
|
debug!("cursor became: {:?}", cursor);
|
||||||
|
}
|
||||||
(c, State::Normal) => {
|
(c, State::Normal) => {
|
||||||
grid.lock().unwrap()[*cursor].set_ch(c as char);
|
grid[*cursor].set_ch(c as char);
|
||||||
debug!("setting cell {:?} char '{}'", cursor, c as char);
|
debug!("setting cell {:?} char '{}'", cursor, c as char);
|
||||||
increase_cursor_x!();
|
increase_cursor_x!();
|
||||||
}
|
}
|
||||||
(b'u', State::Csi) => {
|
(b'u', State::Csi) => {
|
||||||
/* restore cursor */
|
/* restore cursor */
|
||||||
debug!("sending {}", EscCode::from((&(*state), byte)));
|
debug!("ignoring {}", EscCode::from((&(*state), byte)));
|
||||||
*state = State::Normal;
|
*state = State::Normal;
|
||||||
}
|
}
|
||||||
(b'm', State::Csi) => {
|
(b'm', State::Csi) => {
|
||||||
/* Character Attributes (SGR). Ps = 0 -> Normal (default), VT100 */
|
/* Character Attributes (SGR). Ps = 0 -> Normal (default), VT100 */
|
||||||
debug!("sending {}", EscCode::from((&(*state), byte)));
|
debug!("ignoring {}", EscCode::from((&(*state), byte)));
|
||||||
*state = State::Normal;
|
*state = State::Normal;
|
||||||
}
|
}
|
||||||
(b'H', State::Csi) => {
|
(b'H', State::Csi) => {
|
||||||
/* move cursor to (1,1) */
|
/* move cursor to (1,1) */
|
||||||
|
|
||||||
debug!("sending {}", EscCode::from((&(*state), byte)),);
|
debug!("sending {}", EscCode::from((&(*state), byte)),);
|
||||||
debug!("move cursor to (1,1) cursor before: {:?}", *cursor);
|
debug!("move cursor to (1,1) cursor before: {:?}", *cursor);
|
||||||
*cursor = (0, 0);
|
*cursor = (0, 0);
|
||||||
debug!("cursor after: {:?}", *cursor);
|
debug!("cursor after: {:?}", *cursor);
|
||||||
*state = State::Normal;
|
*state = State::Normal;
|
||||||
}
|
}
|
||||||
|
(b'P', State::Csi) => {
|
||||||
|
/* delete one character */
|
||||||
|
debug!("sending {}", EscCode::from((&(*state), byte)),);
|
||||||
|
grid[*cursor].set_ch(' ');
|
||||||
|
*state = State::Normal;
|
||||||
|
}
|
||||||
|
(b'C', State::Csi) => {
|
||||||
|
// "ESC[C\t\tCSI Cursor Forward one Time",
|
||||||
|
debug!("cursor forward one time, cursor was: {:?}", cursor);
|
||||||
|
cursor.0 = std::cmp::min(cursor.0 + 1, terminal_size.0.saturating_sub(1));
|
||||||
|
debug!("cursor became: {:?}", cursor);
|
||||||
|
*state = State::Normal;
|
||||||
|
}
|
||||||
/* CSI ? stuff */
|
/* CSI ? stuff */
|
||||||
(c, State::CsiQ(ref mut buf)) if c >= b'0' && c <= b'9' => {
|
(c, State::CsiQ(ref mut buf)) if c >= b'0' && c <= b'9' => {
|
||||||
buf.push(c);
|
buf.push(c);
|
||||||
|
@ -132,7 +218,7 @@ impl EmbedGrid {
|
||||||
(c, State::CsiQ(ref mut buf)) => {
|
(c, State::CsiQ(ref mut buf)) => {
|
||||||
// we are already in AlternativeScreen so do not forward this
|
// we are already in AlternativeScreen so do not forward this
|
||||||
if &buf.as_slice() != &SWITCHALTERNATIVE_1049 {
|
if &buf.as_slice() != &SWITCHALTERNATIVE_1049 {
|
||||||
debug!("sending {}", EscCode::from((&(*state), byte)));
|
debug!("ignoring {}", EscCode::from((&(*state), byte)));
|
||||||
}
|
}
|
||||||
*state = State::Normal;
|
*state = State::Normal;
|
||||||
}
|
}
|
||||||
|
@ -146,71 +232,148 @@ impl EmbedGrid {
|
||||||
*state = State::Csi1(buf1);
|
*state = State::Csi1(buf1);
|
||||||
}
|
}
|
||||||
(b'J', State::Csi) => {
|
(b'J', State::Csi) => {
|
||||||
// "ESC[J\t\tCSI Erase from the cursor to the end of the screen [BAD]"
|
/* Erase in Display (ED), VT100.*/
|
||||||
debug!("sending {}", EscCode::from((&(*state), byte)));
|
/* Erase Below (default). */
|
||||||
let mut grid = grid.lock().unwrap();
|
clear_area(
|
||||||
debug!("erasing from {:?} to {:?}", cursor, terminal_size);
|
grid,
|
||||||
for y in cursor.1..terminal_size.1 {
|
(
|
||||||
for x in cursor.0..terminal_size.0 {
|
(
|
||||||
cursor.0 = x;
|
0,
|
||||||
grid[(x, y)] = Cell::default();
|
std::cmp::min(cursor.1 + 1, terminal_size.1.saturating_sub(1)),
|
||||||
}
|
),
|
||||||
cursor.1 = y;
|
(
|
||||||
}
|
terminal_size.0.saturating_sub(1),
|
||||||
|
terminal_size.1.saturating_sub(1),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
debug!("{}", EscCode::from((&(*state), byte)));
|
||||||
*state = State::Normal;
|
*state = State::Normal;
|
||||||
}
|
}
|
||||||
(b'K', State::Csi) => {
|
(b'K', State::Csi) => {
|
||||||
// "ESC[K\t\tCSI Erase from the cursor to the end of the line [BAD]"
|
/* Erase in Line (ED), VT100.*/
|
||||||
debug!("sending {}", EscCode::from((&(*state), byte)));
|
/* Erase to right (Default) */
|
||||||
let mut grid = grid.lock().unwrap();
|
debug!("{}", EscCode::from((&(*state), byte)));
|
||||||
for x in cursor.0..terminal_size.0 {
|
for x in cursor.0..terminal_size.0 {
|
||||||
grid[(x, terminal_size.1)] = Cell::default();
|
grid[(x, cursor.1)] = Cell::default();
|
||||||
}
|
}
|
||||||
*state = State::Normal;
|
*state = State::Normal;
|
||||||
}
|
}
|
||||||
(c, State::Csi) => {
|
(c, State::Csi) => {
|
||||||
debug!("sending {}", EscCode::from((&(*state), byte)));
|
debug!("ignoring {}", EscCode::from((&(*state), byte)));
|
||||||
*state = State::Normal;
|
*state = State::Normal;
|
||||||
}
|
}
|
||||||
(b'K', State::Csi1(_)) => {
|
(b'K', State::Csi1(buf)) if buf == b"0" => {
|
||||||
/* Erase in Display (ED), VT100.*/
|
/* Erase in Line (ED), VT100.*/
|
||||||
debug!("not sending {}", EscCode::from((&(*state), byte)));
|
/* Erase to right (Default) */
|
||||||
|
debug!("{}", EscCode::from((&(*state), byte)));
|
||||||
|
for x in cursor.0..terminal_size.0 {
|
||||||
|
grid[(x, cursor.1)] = Cell::default();
|
||||||
|
}
|
||||||
*state = State::Normal;
|
*state = State::Normal;
|
||||||
}
|
}
|
||||||
(b'J', State::Csi1(_)) => {
|
(b'K', State::Csi1(buf)) if buf == b"1" => {
|
||||||
|
/* Erase in Line (ED), VT100.*/
|
||||||
|
/* Erase to left (Default) */
|
||||||
|
for x in cursor.0..=0 {
|
||||||
|
grid[(x, cursor.1)] = Cell::default();
|
||||||
|
}
|
||||||
|
debug!("{}", EscCode::from((&(*state), byte)));
|
||||||
|
*state = State::Normal;
|
||||||
|
}
|
||||||
|
(b'K', State::Csi1(buf)) if buf == b"2" => {
|
||||||
|
/* Erase in Line (ED), VT100.*/
|
||||||
|
/* Erase all */
|
||||||
|
for y in 0..terminal_size.1 {
|
||||||
|
for x in 0..terminal_size.0 {
|
||||||
|
grid[(x, y)] = Cell::default();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
debug!("{}", EscCode::from((&(*state), byte)));
|
||||||
|
clear_area(grid, ((0, 0), pos_dec(*terminal_size, (1, 1))));
|
||||||
|
*state = State::Normal;
|
||||||
|
}
|
||||||
|
(b'J', State::Csi1(ref buf)) if buf == b"0" => {
|
||||||
/* Erase in Display (ED), VT100.*/
|
/* Erase in Display (ED), VT100.*/
|
||||||
debug!("not sending {}", EscCode::from((&(*state), byte)));
|
/* Erase Below (default). */
|
||||||
|
clear_area(
|
||||||
|
grid,
|
||||||
|
(
|
||||||
|
(
|
||||||
|
0,
|
||||||
|
std::cmp::min(cursor.1 + 1, terminal_size.1.saturating_sub(1)),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
terminal_size.0.saturating_sub(1),
|
||||||
|
terminal_size.1.saturating_sub(1),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
debug!("{}", EscCode::from((&(*state), byte)));
|
||||||
|
*state = State::Normal;
|
||||||
|
}
|
||||||
|
(b'J', State::Csi1(ref buf)) if buf == b"1" => {
|
||||||
|
/* Erase in Display (ED), VT100.*/
|
||||||
|
/* Erase Above */
|
||||||
|
clear_area(
|
||||||
|
grid,
|
||||||
|
(
|
||||||
|
(0, 0),
|
||||||
|
(
|
||||||
|
terminal_size.0.saturating_sub(1),
|
||||||
|
cursor.1.saturating_sub(1),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
debug!("{}", EscCode::from((&(*state), byte)));
|
||||||
|
*state = State::Normal;
|
||||||
|
}
|
||||||
|
(b'J', State::Csi1(ref buf)) if buf == b"2" => {
|
||||||
|
/* Erase in Display (ED), VT100.*/
|
||||||
|
/* Erase All */
|
||||||
|
clear_area(grid, ((0, 0), pos_dec(*terminal_size, (1, 1))));
|
||||||
|
debug!("{}", EscCode::from((&(*state), byte)));
|
||||||
|
*state = State::Normal;
|
||||||
|
}
|
||||||
|
(b'J', State::Csi1(ref buf)) if buf == b"3" => {
|
||||||
|
/* Erase in Display (ED), VT100.*/
|
||||||
|
/* Erase saved lines (What?) */
|
||||||
|
debug!("ignoring {}", EscCode::from((&(*state), byte)));
|
||||||
*state = State::Normal;
|
*state = State::Normal;
|
||||||
}
|
}
|
||||||
(b't', State::Csi1(buf)) => {
|
(b't', State::Csi1(buf)) => {
|
||||||
/* Window manipulation, skip it */
|
/* Window manipulation */
|
||||||
if buf == b"18" {
|
if buf == b"18" {
|
||||||
|
debug!("report size of the text area");
|
||||||
|
debug!("got {}", EscCode::from((&(*state), byte)));
|
||||||
// P s = 1 8 → Report the size of the text area in characters as CSI 8 ; height ; width t
|
// P s = 1 8 → Report the size of the text area in characters as CSI 8 ; height ; width t
|
||||||
stdin.write_all(&[b'\x1b', b'[', b'8', b';']).unwrap();
|
stdin.write_all(b"\x1b[8;").unwrap();
|
||||||
stdin
|
stdin
|
||||||
.write_all((terminal_size.0 + 1).to_string().as_bytes())
|
.write_all((terminal_size.1).to_string().as_bytes())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
stdin.write_all(&[b';']).unwrap();
|
stdin.write_all(&[b';']).unwrap();
|
||||||
stdin
|
stdin
|
||||||
.write_all((terminal_size.1 + 1).to_string().as_bytes())
|
.write_all((terminal_size.0).to_string().as_bytes())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
stdin.write_all(&[b't']).unwrap();
|
stdin.write_all(&[b't']).unwrap();
|
||||||
|
} else {
|
||||||
|
debug!("not sending {}", EscCode::from((&(*state), byte)));
|
||||||
}
|
}
|
||||||
debug!("not sending {}", EscCode::from((&(*state), byte)));
|
|
||||||
*state = State::Normal;
|
*state = State::Normal;
|
||||||
}
|
}
|
||||||
(b'n', State::Csi1(_)) => {
|
(b'n', State::Csi1(_)) => {
|
||||||
/* report cursor position */
|
/* report cursor position */
|
||||||
|
debug!("report cursor position");
|
||||||
debug!("got {}", EscCode::from((&(*state), byte)));
|
debug!("got {}", EscCode::from((&(*state), byte)));
|
||||||
stdin.write_all(&[b'\x1b', b'[']).unwrap();
|
stdin.write_all(&[b'\x1b', b'[']).unwrap();
|
||||||
// Ps = 6 ⇒ Report Cursor Position (CPR) [row;column].
|
// Ps = 6 ⇒ Report Cursor Position (CPR) [row;column].
|
||||||
//Result is CSI r ; c R
|
//Result is CSI r ; c R
|
||||||
stdin
|
stdin
|
||||||
.write_all((cursor.0 + 1).to_string().as_bytes())
|
.write_all((cursor.1 + 1).to_string().as_bytes())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
stdin.write_all(&[b';']).unwrap();
|
stdin.write_all(&[b';']).unwrap();
|
||||||
stdin
|
stdin
|
||||||
.write_all((cursor.1 + 1).to_string().as_bytes())
|
.write_all((cursor.0 + 1).to_string().as_bytes())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
stdin.write_all(&[b'R']).unwrap();
|
stdin.write_all(&[b'R']).unwrap();
|
||||||
*state = State::Normal;
|
*state = State::Normal;
|
||||||
|
@ -262,7 +425,7 @@ impl EmbedGrid {
|
||||||
);
|
);
|
||||||
if offset + cursor.1 < terminal_size.1 {
|
if offset + cursor.1 < terminal_size.1 {
|
||||||
cursor.1 += offset;
|
cursor.1 += offset;
|
||||||
cursor.0 = 0;
|
//cursor.0 = 0;
|
||||||
}
|
}
|
||||||
debug!("cursor became: {:?}", cursor);
|
debug!("cursor became: {:?}", cursor);
|
||||||
*state = State::Normal;
|
*state = State::Normal;
|
||||||
|
@ -273,14 +436,19 @@ impl EmbedGrid {
|
||||||
.parse::<usize>()
|
.parse::<usize>()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
debug!("cursor absolute {}, cursor was: {:?}", new_col, cursor);
|
debug!("cursor absolute {}, cursor was: {:?}", new_col, cursor);
|
||||||
if new_col < terminal_size.0 {
|
if new_col < terminal_size.0 + 1 {
|
||||||
cursor.0 = new_col;
|
cursor.0 = new_col.saturating_sub(1);
|
||||||
|
} else {
|
||||||
|
debug!(
|
||||||
|
"error: new_cal = {} > terminal.size.0 = {}\nterminal_size = {:?}",
|
||||||
|
new_col, terminal_size.0, terminal_size
|
||||||
|
);
|
||||||
}
|
}
|
||||||
debug!("cursor became: {:?}", cursor);
|
debug!("cursor became: {:?}", cursor);
|
||||||
*state = State::Normal;
|
*state = State::Normal;
|
||||||
}
|
}
|
||||||
(b'C', State::Csi1(buf)) => {
|
(b'C', State::Csi1(buf)) => {
|
||||||
// "ESC[{buf}F\t\tCSI Cursor Preceding Line {buf} Times",
|
// "ESC[{buf}C\t\tCSI Cursor Preceding Line {buf} Times",
|
||||||
let offset = unsafe { std::str::from_utf8_unchecked(buf) }
|
let offset = unsafe { std::str::from_utf8_unchecked(buf) }
|
||||||
.parse::<usize>()
|
.parse::<usize>()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -288,13 +456,41 @@ impl EmbedGrid {
|
||||||
"cursor preceding {} times, cursor was: {:?}",
|
"cursor preceding {} times, cursor was: {:?}",
|
||||||
offset, cursor
|
offset, cursor
|
||||||
);
|
);
|
||||||
if cursor.1 < offset + terminal_size.1 {
|
if cursor.1 >= offset {
|
||||||
cursor.1 -= offset;
|
cursor.1 -= offset;
|
||||||
cursor.0 = 0;
|
//cursor.0 = 0;
|
||||||
}
|
}
|
||||||
debug!("cursor became: {:?}", cursor);
|
debug!("cursor became: {:?}", cursor);
|
||||||
*state = State::Normal;
|
*state = State::Normal;
|
||||||
}
|
}
|
||||||
|
(b'P', State::Csi1(buf)) => {
|
||||||
|
// "ESC[{buf}P\t\tCSI Delete {buf} characters, default = 1",
|
||||||
|
let offset = unsafe { std::str::from_utf8_unchecked(buf) }
|
||||||
|
.parse::<usize>()
|
||||||
|
.unwrap();
|
||||||
|
debug!(
|
||||||
|
"Delete {} Character(s) with cursor at {:?} ",
|
||||||
|
offset, cursor
|
||||||
|
);
|
||||||
|
for x in (cursor.0 - std::cmp::min(offset, cursor.0))..cursor.0 {
|
||||||
|
grid[(x, cursor.1)].set_ch(' ');
|
||||||
|
}
|
||||||
|
debug!("cursor became: {:?}", cursor);
|
||||||
|
*state = State::Normal;
|
||||||
|
}
|
||||||
|
/* CSI Pm d Line Position Absolute [row] (default = [1,column]) (VPA). */
|
||||||
|
(b'd', State::Csi1(buf)) => {
|
||||||
|
let row = unsafe { std::str::from_utf8_unchecked(buf) }
|
||||||
|
.parse::<usize>()
|
||||||
|
.unwrap();
|
||||||
|
debug!(
|
||||||
|
"Line position absolute row {} with cursor at {:?}",
|
||||||
|
row, cursor
|
||||||
|
);
|
||||||
|
cursor.1 = std::cmp::min(row.saturating_sub(1), terminal_size.1.saturating_sub(1));
|
||||||
|
debug!("cursor became: {:?}", cursor);
|
||||||
|
*state = State::Normal;
|
||||||
|
}
|
||||||
(b';', State::Csi1(ref mut buf1_p)) => {
|
(b';', State::Csi1(ref mut buf1_p)) => {
|
||||||
let buf1 = std::mem::replace(buf1_p, Vec::new());
|
let buf1 = std::mem::replace(buf1_p, Vec::new());
|
||||||
let buf2 = Vec::new();
|
let buf2 = Vec::new();
|
||||||
|
@ -304,7 +500,7 @@ impl EmbedGrid {
|
||||||
buf.push(c);
|
buf.push(c);
|
||||||
}
|
}
|
||||||
(c, State::Csi1(ref buf)) => {
|
(c, State::Csi1(ref buf)) => {
|
||||||
debug!("sending {}", EscCode::from((&(*state), byte)));
|
debug!("ignoring {}", EscCode::from((&(*state), byte)));
|
||||||
*state = State::Normal;
|
*state = State::Normal;
|
||||||
}
|
}
|
||||||
(b';', State::Csi2(ref mut buf1_p, ref mut buf2_p)) => {
|
(b';', State::Csi2(ref mut buf1_p, ref mut buf2_p)) => {
|
||||||
|
@ -314,29 +510,36 @@ impl EmbedGrid {
|
||||||
*state = State::Csi3(buf1, buf2, buf3);
|
*state = State::Csi3(buf1, buf2, buf3);
|
||||||
}
|
}
|
||||||
(b'n', State::Csi2(_, _)) => {
|
(b'n', State::Csi2(_, _)) => {
|
||||||
|
debug!("ignoring {}", EscCode::from((&(*state), byte)));
|
||||||
// Report Cursor Position, skip it
|
// Report Cursor Position, skip it
|
||||||
*state = State::Normal;
|
*state = State::Normal;
|
||||||
}
|
}
|
||||||
(b't', State::Csi2(_, _)) => {
|
(b't', State::Csi2(_, _)) => {
|
||||||
|
debug!("ignoring {}", EscCode::from((&(*state), byte)));
|
||||||
// Window manipulation, skip it
|
// Window manipulation, skip it
|
||||||
*state = State::Normal;
|
*state = State::Normal;
|
||||||
}
|
}
|
||||||
(b'H', State::Csi2(ref x, ref y)) => {
|
(b'H', State::Csi2(ref y, ref x)) => {
|
||||||
//Cursor Position [row;column] (default = [1,1]) (CUP).
|
//Cursor Position [row;column] (default = [1,1]) (CUP).
|
||||||
let orig_x = unsafe { std::str::from_utf8_unchecked(x) }
|
let orig_x = unsafe { std::str::from_utf8_unchecked(x) }
|
||||||
.parse::<usize>()
|
.parse::<usize>()
|
||||||
.unwrap();
|
.unwrap_or(1);
|
||||||
let orig_y = unsafe { std::str::from_utf8_unchecked(y) }
|
let orig_y = unsafe { std::str::from_utf8_unchecked(y) }
|
||||||
.parse::<usize>()
|
.parse::<usize>()
|
||||||
.unwrap();
|
.unwrap_or(1);
|
||||||
debug!("sending {}", EscCode::from((&(*state), byte)),);
|
debug!("sending {}", EscCode::from((&(*state), byte)),);
|
||||||
debug!(
|
debug!(
|
||||||
"cursor set to ({},{}), cursor was: {:?}",
|
"cursor set to ({},{}), cursor was: {:?}",
|
||||||
orig_x, orig_y, cursor
|
orig_x, orig_y, cursor
|
||||||
);
|
);
|
||||||
if orig_x - 1 <= terminal_size.0 && orig_y - 1 <= terminal_size.1 {
|
if orig_x - 1 < terminal_size.0 && orig_y - 1 < terminal_size.1 {
|
||||||
cursor.0 = orig_x - 1;
|
cursor.0 = orig_x - 1;
|
||||||
cursor.1 = orig_y - 1;
|
cursor.1 = orig_y - 1;
|
||||||
|
} else {
|
||||||
|
debug!(
|
||||||
|
"[error] terminal_size = {:?}, cursor = {:?} but given [{},{}]",
|
||||||
|
terminal_size, cursor, orig_x, orig_y
|
||||||
|
);
|
||||||
}
|
}
|
||||||
debug!("cursor became: {:?}", cursor);
|
debug!("cursor became: {:?}", cursor);
|
||||||
*state = State::Normal;
|
*state = State::Normal;
|
||||||
|
@ -345,10 +548,11 @@ impl EmbedGrid {
|
||||||
buf.push(c);
|
buf.push(c);
|
||||||
}
|
}
|
||||||
(c, State::Csi2(ref buf1, ref buf2)) => {
|
(c, State::Csi2(ref buf1, ref buf2)) => {
|
||||||
debug!("sending {}", EscCode::from((&(*state), byte)));
|
debug!("ignoring {}", EscCode::from((&(*state), byte)));
|
||||||
*state = State::Normal;
|
*state = State::Normal;
|
||||||
}
|
}
|
||||||
(b't', State::Csi3(_, _, _)) => {
|
(b't', State::Csi3(_, _, _)) => {
|
||||||
|
debug!("ignoring {}", EscCode::from((&(*state), byte)));
|
||||||
// Window manipulation, skip it
|
// Window manipulation, skip it
|
||||||
*state = State::Normal;
|
*state = State::Normal;
|
||||||
}
|
}
|
||||||
|
@ -356,8 +560,8 @@ impl EmbedGrid {
|
||||||
(c, State::Csi3(_, _, ref mut buf)) if c >= b'0' && c <= b'9' => {
|
(c, State::Csi3(_, _, ref mut buf)) if c >= b'0' && c <= b'9' => {
|
||||||
buf.push(c);
|
buf.push(c);
|
||||||
}
|
}
|
||||||
(c, State::Csi3(ref buf1, ref buf2, ref buf3)) => {
|
(c, State::Csi3(_, _, _)) => {
|
||||||
debug!("sending {}", EscCode::from((&(*state), byte)));
|
debug!("ignoring {}", EscCode::from((&(*state), byte)));
|
||||||
*state = State::Normal;
|
*state = State::Normal;
|
||||||
}
|
}
|
||||||
/* other stuff */
|
/* other stuff */
|
||||||
|
@ -365,7 +569,7 @@ impl EmbedGrid {
|
||||||
/* ******************* */
|
/* ******************* */
|
||||||
/* ******************* */
|
/* ******************* */
|
||||||
(c, State::G0) => {
|
(c, State::G0) => {
|
||||||
debug!("sending {}", EscCode::from((&(*state), byte)));
|
debug!("ignoring {}", EscCode::from((&(*state), byte)));
|
||||||
*state = State::Normal;
|
*state = State::Normal;
|
||||||
}
|
}
|
||||||
(b, s) => {
|
(b, s) => {
|
||||||
|
|
|
@ -139,8 +139,17 @@ impl PartialEq<Key> for &Key {
|
||||||
enum InputMode {
|
enum InputMode {
|
||||||
Normal,
|
Normal,
|
||||||
Paste,
|
Paste,
|
||||||
|
PasteRaw(Vec<u8>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum InputCommand {
|
||||||
|
Kill,
|
||||||
|
/// Send Raw bytes as well
|
||||||
|
Raw,
|
||||||
|
NoRaw,
|
||||||
|
}
|
||||||
|
|
||||||
|
use termion::input::TermReadEventsAndRaw;
|
||||||
/*
|
/*
|
||||||
* If we fork (for example start $EDITOR) we want the input-thread to stop reading from stdin. The
|
* If we fork (for example start $EDITOR) we want the input-thread to stop reading from stdin. The
|
||||||
* best way I came up with right now is to send a signal to the thread that is read in the first
|
* best way I came up with right now is to send a signal to the thread that is read in the first
|
||||||
|
@ -150,22 +159,21 @@ enum InputMode {
|
||||||
* The main loop uses try_wait_on_child() to check if child has exited.
|
* The main loop uses try_wait_on_child() to check if child has exited.
|
||||||
*/
|
*/
|
||||||
pub fn get_events(
|
pub fn get_events(
|
||||||
stdin: io::Stdin,
|
|
||||||
mut closure: impl FnMut(Key),
|
mut closure: impl FnMut(Key),
|
||||||
mut exit: impl FnMut(),
|
closure_raw: impl FnMut((Key, Vec<u8>)),
|
||||||
rx: &Receiver<bool>,
|
rx: &Receiver<InputCommand>,
|
||||||
) {
|
) -> ! {
|
||||||
|
let stdin = std::io::stdin();
|
||||||
let mut input_mode = InputMode::Normal;
|
let mut input_mode = InputMode::Normal;
|
||||||
let mut paste_buf = String::with_capacity(256);
|
let mut paste_buf = String::with_capacity(256);
|
||||||
for c in stdin.events() {
|
for c in stdin.events() {
|
||||||
select! {
|
select! {
|
||||||
default => {},
|
default => {},
|
||||||
recv(rx) -> val => {
|
recv(rx) -> cmd => {
|
||||||
if let Ok(true) = val {
|
match cmd.unwrap() {
|
||||||
exit();
|
InputCommand::Kill => std::process::exit(0),
|
||||||
return;
|
InputCommand::Raw => get_events_raw(closure, closure_raw, rx),
|
||||||
} else {
|
InputCommand::NoRaw => unreachable!(),
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -189,6 +197,59 @@ pub fn get_events(
|
||||||
_ => {} // Mouse events or errors.
|
_ => {} // Mouse events or errors.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
std::process::exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_events_raw(
|
||||||
|
closure_nonraw: impl FnMut(Key),
|
||||||
|
mut closure: impl FnMut((Key, Vec<u8>)),
|
||||||
|
rx: &Receiver<InputCommand>,
|
||||||
|
) -> ! {
|
||||||
|
let stdin = std::io::stdin();
|
||||||
|
let mut input_mode = InputMode::Normal;
|
||||||
|
let mut paste_buf = String::with_capacity(256);
|
||||||
|
for c in stdin.events_and_raw() {
|
||||||
|
select! {
|
||||||
|
default => {},
|
||||||
|
recv(rx) -> cmd => {
|
||||||
|
match cmd.unwrap() {
|
||||||
|
InputCommand::Kill => std::process::exit(0),
|
||||||
|
InputCommand::NoRaw => get_events(closure_nonraw, closure, rx),
|
||||||
|
InputCommand::Raw => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match (c, &mut input_mode) {
|
||||||
|
(Ok((TermionEvent::Key(k), bytes)), InputMode::Normal) => {
|
||||||
|
closure((Key::from(k), bytes));
|
||||||
|
}
|
||||||
|
(
|
||||||
|
Ok((TermionEvent::Key(TermionKey::Char(k)), ref mut bytes)),
|
||||||
|
InputMode::PasteRaw(ref mut buf),
|
||||||
|
) => {
|
||||||
|
paste_buf.push(k);
|
||||||
|
let bytes = std::mem::replace(bytes, Vec::new());
|
||||||
|
buf.extend(bytes.into_iter());
|
||||||
|
}
|
||||||
|
(Ok((TermionEvent::Unsupported(ref k), _)), _)
|
||||||
|
if k.as_slice() == BRACKET_PASTE_START =>
|
||||||
|
{
|
||||||
|
input_mode = InputMode::PasteRaw(Vec::new());
|
||||||
|
}
|
||||||
|
(Ok((TermionEvent::Unsupported(ref k), _)), InputMode::PasteRaw(ref mut buf))
|
||||||
|
if k.as_slice() == BRACKET_PASTE_END =>
|
||||||
|
{
|
||||||
|
let buf = std::mem::replace(buf, Vec::new());
|
||||||
|
input_mode = InputMode::Normal;
|
||||||
|
let ret = Key::from(&paste_buf);
|
||||||
|
paste_buf.clear();
|
||||||
|
closure((ret, buf));
|
||||||
|
}
|
||||||
|
_ => {} // Mouse events or errors.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::process::exit(0)
|
||||||
}
|
}
|
||||||
const FIELDS: &[&str] = &[];
|
const FIELDS: &[&str] = &[];
|
||||||
|
|
||||||
|
|
|
@ -48,6 +48,8 @@ pub enum ThreadEvent {
|
||||||
ThreadJoin(thread::ThreadId),
|
ThreadJoin(thread::ThreadId),
|
||||||
/// User input.
|
/// User input.
|
||||||
Input(Key),
|
Input(Key),
|
||||||
|
/// User input and input as raw bytes.
|
||||||
|
InputRaw((Key, Vec<u8>)),
|
||||||
/// A watched folder has been refreshed.
|
/// A watched folder has been refreshed.
|
||||||
RefreshMailbox(Box<RefreshEvent>),
|
RefreshMailbox(Box<RefreshEvent>),
|
||||||
UIEvent(UIEvent),
|
UIEvent(UIEvent),
|
||||||
|
@ -84,9 +86,11 @@ pub enum UIEvent {
|
||||||
Input(Key),
|
Input(Key),
|
||||||
ExInput(Key),
|
ExInput(Key),
|
||||||
InsertInput(Key),
|
InsertInput(Key),
|
||||||
EmbedInput(Key),
|
EmbedInput((Key, Vec<u8>)),
|
||||||
RefreshMailbox((usize, FolderHash)), //view has changed to FolderHash mailbox
|
RefreshMailbox((usize, FolderHash)), //view has changed to FolderHash mailbox
|
||||||
//Quit?
|
ChildStatusExited(nix::unistd::Pid, i32),
|
||||||
|
ChildStatusStopped(nix::unistd::Pid),
|
||||||
|
ChildStatusContinued(nix::unistd::Pid),
|
||||||
Resize,
|
Resize,
|
||||||
/// Force redraw.
|
/// Force redraw.
|
||||||
Fork(ForkType),
|
Fork(ForkType),
|
||||||
|
|
Loading…
Reference in New Issue