frozen_term/
local_terminal.rs1use std::{sync::Arc, time::Duration};
2
3use crate::{Style, terminal};
4use async_pty::PtyProcess;
5use iced::{
6 self, Element, Length, Task,
7 task::sipper,
8 widget::{center, text},
9};
10
11#[derive(Debug, Clone)]
12pub struct Message(InnerMessage);
13
14#[derive(Debug, Clone)]
15enum InnerMessage {
16 Opened(Arc<(PtyProcess, tokio::sync::mpsc::Receiver<Vec<u8>>)>),
17 Terminal(terminal::Message),
18 Output(Vec<u8>),
19 InjectInput(Vec<u8>),
20 Closed,
21}
22
23pub enum Action {
24 Run(Task<Message>),
25 IdChanged,
26 Close,
27 None,
28}
29
30enum State {
31 Starting,
32 Active(PtyProcess),
33 Closed,
34}
35
36pub struct LocalTerminal {
37 state: State,
38 display: terminal::Terminal,
39}
40
41impl LocalTerminal {
42 pub fn start(
43 key_filter: impl 'static + Fn(&iced::keyboard::Key, &iced::keyboard::Modifiers) -> bool,
44 ) -> (Self, Task<Message>) {
45 let size = async_pty::TerminalSize { cols: 80, rows: 24 };
46 let (display, display_task) = terminal::Terminal::new();
47 let display = display.key_filter(key_filter);
48
49 let start_task = Task::future(async {
50 let (process, output) = PtyProcess::shell(size).await.unwrap();
51 Message(InnerMessage::Opened(Arc::new((process, output))))
52 });
53
54 (
55 Self {
56 state: State::Starting,
57 display,
58 },
59 Task::batch([
60 display_task.map(InnerMessage::Terminal).map(Message),
61 start_task,
62 ]),
63 )
64 }
65
66 pub fn style(mut self, style: Style) -> Self {
67 self.set_style(style);
68 self
69 }
70
71 pub fn set_style(&mut self, style: Style) {
72 self.display.set_style(style);
73 }
74
75 #[must_use]
76 pub fn update(&mut self, message: Message) -> Action {
77 match message.0 {
78 InnerMessage::Opened(arc) => {
79 let (process, output) = Arc::into_inner(arc).unwrap();
80
81 let stream = sipper(|mut sender| async move {
82 let mut output = output;
83 while let Some(chunk) = output.recv().await {
84 sender.send(InnerMessage::Output(chunk)).await;
85 }
86
87 sender.send(InnerMessage::Closed).await;
88 });
89
90 let task = Task::stream(stream).map(Message);
91
92 self.state = State::Active(process);
93
94 Action::Run(task)
95 }
96 InnerMessage::Terminal(message) => {
97 let action = self.display.update(message);
98
99 match action {
100 terminal::Action::None => Action::None,
101 terminal::Action::Run(task) => {
102 Action::Run(task.map(InnerMessage::Terminal).map(Message))
103 }
104 terminal::Action::IdChanged => Action::IdChanged,
105 terminal::Action::Input(input) => {
106 if let State::Active(pty) = &self.state {
107 pty.try_write(input).unwrap();
108 }
109 Action::None
110 }
111 terminal::Action::Resize(size) => {
112 if let State::Active(pty) = &self.state {
113 pty.try_resize(async_pty::TerminalSize {
114 rows: size.rows as u16,
115 cols: size.cols as u16,
116 })
117 .unwrap();
118 }
119 Action::None
120 }
121 }
122 }
123 InnerMessage::InjectInput(input) => {
124 if let State::Active(pty) = &self.state {
125 pty.try_write(input).unwrap();
126 }
127 Action::None
128 }
129 InnerMessage::Output(output) => {
130 self.display.advance_bytes(output);
131
132 Action::None
133 }
134 InnerMessage::Closed => {
135 self.state = State::Closed;
136
137 Action::Close
138 }
139 }
140 }
141
142 pub fn view<'a>(&'a self) -> Element<'a, Message> {
143 match &self.state {
144 State::Starting => center(text!("opening pty...")).into(),
145 State::Active(_) => self.display.view().map(InnerMessage::Terminal).map(Message),
146 State::Closed => center(text!("pty closed")).height(Length::Fill).into(),
147 }
148 }
149
150 pub fn get_title(&self) -> &str {
151 self.display.get_title()
152 }
153
154 #[must_use]
155 pub fn focus<T>(&self) -> Task<T>
156 where
157 T: Send + 'static,
158 {
159 self.display.focus()
160 }
161
162 #[must_use]
169 pub fn inject_input(&self, input: InputSequence) -> Task<Message> {
170 if let State::Active(ref pty) = self.state {
171 match input {
172 InputSequence::Raw(input) => {
173 let _ = pty.try_write(input);
174 Task::none()
175 }
176 InputSequence::AbortAndRaw(input) => {
177 let _ = pty.try_write(b"\x03".to_vec());
178 Task::future(async move {
181 tokio::time::sleep(INJECTION_DELAY).await;
182 Message(InnerMessage::InjectInput(input))
183 })
184 }
185 InputSequence::AbortAndCommand(mut input) => {
186 let _ = pty.try_write(b"\x03".to_vec());
187 input.push('\n');
188 let input = input.into_bytes();
189 Task::future(async move {
190 tokio::time::sleep(INJECTION_DELAY).await;
191 Message(InnerMessage::InjectInput(input))
192 })
193 }
194 }
195 } else {
196 Task::none()
197 }
198 }
199}
200
201const INJECTION_DELAY: Duration = Duration::from_millis(100);
202
203pub enum InputSequence {
204 Raw(Vec<u8>),
209 AbortAndRaw(Vec<u8>),
212 AbortAndCommand(String),
217}