pub struct Session<M = ()> { /* private fields */ }Expand description
A connection to a terminal that speaks TSP. Dropping it closes it (surfaces, modes, tty).
Implementations§
Source§impl<M> Session<M>
impl<M> Session<M>
Sourcepub fn connect(options: Options) -> Result<Option<Self>, Error>
pub fn connect(options: Options) -> Result<Option<Self>, Error>
Connects to the process’s terminal (Unix and Windows only): Ok(None)
when TSP isn’t available (TERN_TSP=0, stdin or stdout not a tty,
inside screen or zellij, inside tmux unless Tern draws its session, or
the terminal answers DA1 first or not in time). Inside tmux it sends
hello without DA1 (tmux answers DA1 itself) and turns the pane’s
allow-set-title off until the session ends.
§Errors
When the tty can’t be switched to raw mode or I/O fails.
Examples found in repository?
101fn main() -> Result<(), tern_sdk::Error> {
102 let Some(mut session) = Session::connect(Options::new())? else {
103 for (i, step) in STEPS.iter().enumerate() {
104 println!("[{}/{}] {step}", i + 1, STEPS.len());
105 std::thread::sleep(STEP);
106 }
107 println!("Built demo.");
108 return Ok(());
109 };
110 let start = Instant::now();
111 let sf = session.open(SurfaceOptions::flow().title("progress"))?;
112 let mut build =
113 Build { log: String::new(), done: 0, took: None, status: Status::Running };
114 session.render(sf, view(&build))?;
115 for step in STEPS {
116 if wait(&mut session, STEP)? {
117 build.status = Status::Cancelled;
118 break;
119 }
120 build.log.push_str("\x1b[32m✓\x1b[0m ");
121 build.log.push_str(step);
122 build.log.push_str("\r\n");
123 build.done += 1;
124 session.render(sf, view(&build))?;
125 }
126 if build.status == Status::Running {
127 build.status = Status::Done;
128 }
129 let took = start.elapsed();
130 build.took = Some(took.as_millis() as u64);
131 session.render(sf, view(&build))?;
132 session.close()?;
133 match build.status {
134 Status::Done => println!("Built demo in {:.1}s.", took.as_secs_f64()),
135 _ => println!("Build cancelled."),
136 }
137 Ok(())
138}More examples
1121fn main() -> Result<(), tern_sdk::Error> {
1122 let mut dash = Dash::new();
1123 let Some(mut session) = Session::connect(Options::new().app("dashboard"))? else {
1124 print!("{}", dash.view(true).plain(100));
1125 return Ok(());
1126 };
1127 let sf = session.open(SurfaceOptions::screen().title("Fleet").font(Font::Sans))?;
1128 session.render(sf, dash.view(false))?;
1129 let mut next = Instant::now() + TICK;
1130 loop {
1131 let left = next.saturating_duration_since(Instant::now());
1132 match session.next(Some(left))? {
1133 None => {
1134 dash.tick();
1135 next = Instant::now() + TICK;
1136 },
1137 Some(Input::Msg(msg, _)) => dash.update(msg),
1138 Some(Input::Key(key)) => {
1139 if key.is("escape") && dash.confirming.is_some() {
1140 dash.update(Msg::Keep);
1141 } else if key.is("enter") && dash.confirming.is_some() {
1142 dash.update(Msg::Confirm);
1143 } else if key.is("escape") || key.is("q") || key.is("ctrl+c") {
1144 break;
1145 } else if key.is("tab") || key.is("]") {
1146 dash.page = dash.page.turn(false);
1147 } else if key.is("shift+tab") || key.is("[") {
1148 dash.page = dash.page.turn(true);
1149 } else if key.is("up") || key.is("k") {
1150 dash.pick(dash.page, -1);
1151 } else if key.is("down") || key.is("j") {
1152 dash.pick(dash.page, 1);
1153 } else if key.is("r") {
1154 dash.update(Msg::Refresh);
1155 } else if key.is("a") {
1156 dash.act_selected();
1157 } else if key.is("enter") {
1158 if let Some(id) = dash.selected[dash.page.index()] {
1159 dash.update(Msg::Open(dash.page, id.into()));
1160 }
1161 } else {
1162 continue;
1163 }
1164 },
1165 Some(Input::Event(_) | Input::Clipboard(_)) => continue,
1166 }
1167 session.render(sf, dash.view(false))?;
1168 }
1169 session.close()?;
1170 Ok(())
1171}128fn main() -> Result<(), tern_sdk::Error> {
129 let Some(mut session) = Session::connect(Options::new().feature(feature::SEND))? else {
130 return plain();
131 };
132 let sf = session.open(SurfaceOptions::inline().title("chat"))?;
133 let mut chat = Chat::default();
134 session.render(sf, chat.view())?;
135 session.focus(sf, Some(EDITOR))?;
136 while let Some(input) = session.next(None)? {
137 match input {
138 Input::Key(key) => {
139 if key.is("escape") || key.is("ctrl+c") || key.is("ctrl+d") {
140 break;
141 }
142 match key.name.as_str() {
143 "enter" if !key.shift && !key.alt => {
144 let text = std::mem::take(&mut chat.draft);
145 chat.cursor = 0;
146 chat.send(text);
147 },
148 "enter" => chat.insert("\n"),
149 "backspace" if chat.cursor > 0 => {
150 let at = chat.byte(chat.cursor - 1);
151 chat.draft.remove(at);
152 chat.cursor -= 1;
153 },
154 "delete" if chat.cursor < chat.draft.chars().count() => {
155 let at = chat.byte(chat.cursor);
156 chat.draft.remove(at);
157 },
158 "left" => chat.cursor = chat.cursor.saturating_sub(1),
159 "right" => chat.cursor = (chat.cursor + 1).min(chat.draft.chars().count()),
160 "home" => chat.cursor = 0,
161 "end" => chat.cursor = chat.draft.chars().count(),
162 "paste" => chat.insert(key.text.as_deref().unwrap_or_default()),
163 _ => match key.typed() {
164 Some(text) => chat.insert(text),
165 None => continue,
166 },
167 }
168 },
169 Input::Msg(Msg::Send(text), _) => chat.send(text),
170 Input::Msg(Msg::Focus, _) => session.focus(sf, Some(EDITOR))?,
171 Input::Event(_) | Input::Clipboard(_) => continue,
172 }
173 session.render(sf, chat.view())?;
174 }
175 session.close()?;
176 println!("Chat closed after {} messages.", chat.transcript.len());
177 Ok(())
178}Sourcepub fn with_terminal(
term: impl Terminal + 'static,
options: Options,
) -> Result<Option<Self>, Error>
pub fn with_terminal( term: impl Terminal + 'static, options: Options, ) -> Result<Option<Self>, Error>
Runs the handshake over term (already in raw mode): writes hello
and a DA1 request and waits for the reply. Ok(None), with the
terminal restored, when DA1 answers first or nothing answers in time.
§Errors
When I/O fails or the recording can’t be opened.
Sourcepub const fn caps(&self) -> &Capabilities
pub const fn caps(&self) -> &Capabilities
What the terminal supports.
Sourcepub fn open(&mut self, options: SurfaceOptions) -> Result<Surface, Error>
pub fn open(&mut self, options: SurfaceOptions) -> Result<Surface, Error>
Examples found in repository?
101fn main() -> Result<(), tern_sdk::Error> {
102 let Some(mut session) = Session::connect(Options::new())? else {
103 for (i, step) in STEPS.iter().enumerate() {
104 println!("[{}/{}] {step}", i + 1, STEPS.len());
105 std::thread::sleep(STEP);
106 }
107 println!("Built demo.");
108 return Ok(());
109 };
110 let start = Instant::now();
111 let sf = session.open(SurfaceOptions::flow().title("progress"))?;
112 let mut build =
113 Build { log: String::new(), done: 0, took: None, status: Status::Running };
114 session.render(sf, view(&build))?;
115 for step in STEPS {
116 if wait(&mut session, STEP)? {
117 build.status = Status::Cancelled;
118 break;
119 }
120 build.log.push_str("\x1b[32m✓\x1b[0m ");
121 build.log.push_str(step);
122 build.log.push_str("\r\n");
123 build.done += 1;
124 session.render(sf, view(&build))?;
125 }
126 if build.status == Status::Running {
127 build.status = Status::Done;
128 }
129 let took = start.elapsed();
130 build.took = Some(took.as_millis() as u64);
131 session.render(sf, view(&build))?;
132 session.close()?;
133 match build.status {
134 Status::Done => println!("Built demo in {:.1}s.", took.as_secs_f64()),
135 _ => println!("Build cancelled."),
136 }
137 Ok(())
138}More examples
1121fn main() -> Result<(), tern_sdk::Error> {
1122 let mut dash = Dash::new();
1123 let Some(mut session) = Session::connect(Options::new().app("dashboard"))? else {
1124 print!("{}", dash.view(true).plain(100));
1125 return Ok(());
1126 };
1127 let sf = session.open(SurfaceOptions::screen().title("Fleet").font(Font::Sans))?;
1128 session.render(sf, dash.view(false))?;
1129 let mut next = Instant::now() + TICK;
1130 loop {
1131 let left = next.saturating_duration_since(Instant::now());
1132 match session.next(Some(left))? {
1133 None => {
1134 dash.tick();
1135 next = Instant::now() + TICK;
1136 },
1137 Some(Input::Msg(msg, _)) => dash.update(msg),
1138 Some(Input::Key(key)) => {
1139 if key.is("escape") && dash.confirming.is_some() {
1140 dash.update(Msg::Keep);
1141 } else if key.is("enter") && dash.confirming.is_some() {
1142 dash.update(Msg::Confirm);
1143 } else if key.is("escape") || key.is("q") || key.is("ctrl+c") {
1144 break;
1145 } else if key.is("tab") || key.is("]") {
1146 dash.page = dash.page.turn(false);
1147 } else if key.is("shift+tab") || key.is("[") {
1148 dash.page = dash.page.turn(true);
1149 } else if key.is("up") || key.is("k") {
1150 dash.pick(dash.page, -1);
1151 } else if key.is("down") || key.is("j") {
1152 dash.pick(dash.page, 1);
1153 } else if key.is("r") {
1154 dash.update(Msg::Refresh);
1155 } else if key.is("a") {
1156 dash.act_selected();
1157 } else if key.is("enter") {
1158 if let Some(id) = dash.selected[dash.page.index()] {
1159 dash.update(Msg::Open(dash.page, id.into()));
1160 }
1161 } else {
1162 continue;
1163 }
1164 },
1165 Some(Input::Event(_) | Input::Clipboard(_)) => continue,
1166 }
1167 session.render(sf, dash.view(false))?;
1168 }
1169 session.close()?;
1170 Ok(())
1171}128fn main() -> Result<(), tern_sdk::Error> {
129 let Some(mut session) = Session::connect(Options::new().feature(feature::SEND))? else {
130 return plain();
131 };
132 let sf = session.open(SurfaceOptions::inline().title("chat"))?;
133 let mut chat = Chat::default();
134 session.render(sf, chat.view())?;
135 session.focus(sf, Some(EDITOR))?;
136 while let Some(input) = session.next(None)? {
137 match input {
138 Input::Key(key) => {
139 if key.is("escape") || key.is("ctrl+c") || key.is("ctrl+d") {
140 break;
141 }
142 match key.name.as_str() {
143 "enter" if !key.shift && !key.alt => {
144 let text = std::mem::take(&mut chat.draft);
145 chat.cursor = 0;
146 chat.send(text);
147 },
148 "enter" => chat.insert("\n"),
149 "backspace" if chat.cursor > 0 => {
150 let at = chat.byte(chat.cursor - 1);
151 chat.draft.remove(at);
152 chat.cursor -= 1;
153 },
154 "delete" if chat.cursor < chat.draft.chars().count() => {
155 let at = chat.byte(chat.cursor);
156 chat.draft.remove(at);
157 },
158 "left" => chat.cursor = chat.cursor.saturating_sub(1),
159 "right" => chat.cursor = (chat.cursor + 1).min(chat.draft.chars().count()),
160 "home" => chat.cursor = 0,
161 "end" => chat.cursor = chat.draft.chars().count(),
162 "paste" => chat.insert(key.text.as_deref().unwrap_or_default()),
163 _ => match key.typed() {
164 Some(text) => chat.insert(text),
165 None => continue,
166 },
167 }
168 },
169 Input::Msg(Msg::Send(text), _) => chat.send(text),
170 Input::Msg(Msg::Focus, _) => session.focus(sf, Some(EDITOR))?,
171 Input::Event(_) | Input::Clipboard(_) => continue,
172 }
173 session.render(sf, chat.view())?;
174 }
175 session.close()?;
176 println!("Chat closed after {} messages.", chat.transcript.len());
177 Ok(())
178}Sourcepub fn surface_id(&self, sf: Surface) -> &str
pub fn surface_id(&self, sf: Surface) -> &str
Sourcepub fn render(
&mut self,
sf: Surface,
view: impl Into<View<M>>,
) -> Result<(), Error>
pub fn render( &mut self, sf: Surface, view: impl Into<View<M>>, ) -> Result<(), Error>
Renders view on sf: sends the difference from the last view sent,
or, while out of credits, stores it for the frame an ack releases.
Its handlers replace the last view’s. A bare node or list is main’s
children.
§Errors
Error::DuplicateId (nothing is sent), a closed surface, or I/O.
Examples found in repository?
101fn main() -> Result<(), tern_sdk::Error> {
102 let Some(mut session) = Session::connect(Options::new())? else {
103 for (i, step) in STEPS.iter().enumerate() {
104 println!("[{}/{}] {step}", i + 1, STEPS.len());
105 std::thread::sleep(STEP);
106 }
107 println!("Built demo.");
108 return Ok(());
109 };
110 let start = Instant::now();
111 let sf = session.open(SurfaceOptions::flow().title("progress"))?;
112 let mut build =
113 Build { log: String::new(), done: 0, took: None, status: Status::Running };
114 session.render(sf, view(&build))?;
115 for step in STEPS {
116 if wait(&mut session, STEP)? {
117 build.status = Status::Cancelled;
118 break;
119 }
120 build.log.push_str("\x1b[32m✓\x1b[0m ");
121 build.log.push_str(step);
122 build.log.push_str("\r\n");
123 build.done += 1;
124 session.render(sf, view(&build))?;
125 }
126 if build.status == Status::Running {
127 build.status = Status::Done;
128 }
129 let took = start.elapsed();
130 build.took = Some(took.as_millis() as u64);
131 session.render(sf, view(&build))?;
132 session.close()?;
133 match build.status {
134 Status::Done => println!("Built demo in {:.1}s.", took.as_secs_f64()),
135 _ => println!("Build cancelled."),
136 }
137 Ok(())
138}More examples
1121fn main() -> Result<(), tern_sdk::Error> {
1122 let mut dash = Dash::new();
1123 let Some(mut session) = Session::connect(Options::new().app("dashboard"))? else {
1124 print!("{}", dash.view(true).plain(100));
1125 return Ok(());
1126 };
1127 let sf = session.open(SurfaceOptions::screen().title("Fleet").font(Font::Sans))?;
1128 session.render(sf, dash.view(false))?;
1129 let mut next = Instant::now() + TICK;
1130 loop {
1131 let left = next.saturating_duration_since(Instant::now());
1132 match session.next(Some(left))? {
1133 None => {
1134 dash.tick();
1135 next = Instant::now() + TICK;
1136 },
1137 Some(Input::Msg(msg, _)) => dash.update(msg),
1138 Some(Input::Key(key)) => {
1139 if key.is("escape") && dash.confirming.is_some() {
1140 dash.update(Msg::Keep);
1141 } else if key.is("enter") && dash.confirming.is_some() {
1142 dash.update(Msg::Confirm);
1143 } else if key.is("escape") || key.is("q") || key.is("ctrl+c") {
1144 break;
1145 } else if key.is("tab") || key.is("]") {
1146 dash.page = dash.page.turn(false);
1147 } else if key.is("shift+tab") || key.is("[") {
1148 dash.page = dash.page.turn(true);
1149 } else if key.is("up") || key.is("k") {
1150 dash.pick(dash.page, -1);
1151 } else if key.is("down") || key.is("j") {
1152 dash.pick(dash.page, 1);
1153 } else if key.is("r") {
1154 dash.update(Msg::Refresh);
1155 } else if key.is("a") {
1156 dash.act_selected();
1157 } else if key.is("enter") {
1158 if let Some(id) = dash.selected[dash.page.index()] {
1159 dash.update(Msg::Open(dash.page, id.into()));
1160 }
1161 } else {
1162 continue;
1163 }
1164 },
1165 Some(Input::Event(_) | Input::Clipboard(_)) => continue,
1166 }
1167 session.render(sf, dash.view(false))?;
1168 }
1169 session.close()?;
1170 Ok(())
1171}128fn main() -> Result<(), tern_sdk::Error> {
129 let Some(mut session) = Session::connect(Options::new().feature(feature::SEND))? else {
130 return plain();
131 };
132 let sf = session.open(SurfaceOptions::inline().title("chat"))?;
133 let mut chat = Chat::default();
134 session.render(sf, chat.view())?;
135 session.focus(sf, Some(EDITOR))?;
136 while let Some(input) = session.next(None)? {
137 match input {
138 Input::Key(key) => {
139 if key.is("escape") || key.is("ctrl+c") || key.is("ctrl+d") {
140 break;
141 }
142 match key.name.as_str() {
143 "enter" if !key.shift && !key.alt => {
144 let text = std::mem::take(&mut chat.draft);
145 chat.cursor = 0;
146 chat.send(text);
147 },
148 "enter" => chat.insert("\n"),
149 "backspace" if chat.cursor > 0 => {
150 let at = chat.byte(chat.cursor - 1);
151 chat.draft.remove(at);
152 chat.cursor -= 1;
153 },
154 "delete" if chat.cursor < chat.draft.chars().count() => {
155 let at = chat.byte(chat.cursor);
156 chat.draft.remove(at);
157 },
158 "left" => chat.cursor = chat.cursor.saturating_sub(1),
159 "right" => chat.cursor = (chat.cursor + 1).min(chat.draft.chars().count()),
160 "home" => chat.cursor = 0,
161 "end" => chat.cursor = chat.draft.chars().count(),
162 "paste" => chat.insert(key.text.as_deref().unwrap_or_default()),
163 _ => match key.typed() {
164 Some(text) => chat.insert(text),
165 None => continue,
166 },
167 }
168 },
169 Input::Msg(Msg::Send(text), _) => chat.send(text),
170 Input::Msg(Msg::Focus, _) => session.focus(sf, Some(EDITOR))?,
171 Input::Event(_) | Input::Clipboard(_) => continue,
172 }
173 session.render(sf, chat.view())?;
174 }
175 session.close()?;
176 println!("Chat closed after {} messages.", chat.transcript.len());
177 Ok(())
178}Sourcepub fn send(&mut self, sf: Surface, ops: Vec<Op>) -> Result<(), Error>
pub fn send(&mut self, sf: Surface, ops: Vec<Op>) -> Result<(), Error>
Sends ops on sf in one frame, after anything waiting, or queues
them while out of credits (the raw escape hatch).
§Errors
A closed surface, or I/O.
Sourcepub fn focus(&mut self, sf: Surface, id: Option<&str>) -> Result<(), Error>
pub fn focus(&mut self, sf: Surface, id: Option<&str>) -> Result<(), Error>
Examples found in repository?
128fn main() -> Result<(), tern_sdk::Error> {
129 let Some(mut session) = Session::connect(Options::new().feature(feature::SEND))? else {
130 return plain();
131 };
132 let sf = session.open(SurfaceOptions::inline().title("chat"))?;
133 let mut chat = Chat::default();
134 session.render(sf, chat.view())?;
135 session.focus(sf, Some(EDITOR))?;
136 while let Some(input) = session.next(None)? {
137 match input {
138 Input::Key(key) => {
139 if key.is("escape") || key.is("ctrl+c") || key.is("ctrl+d") {
140 break;
141 }
142 match key.name.as_str() {
143 "enter" if !key.shift && !key.alt => {
144 let text = std::mem::take(&mut chat.draft);
145 chat.cursor = 0;
146 chat.send(text);
147 },
148 "enter" => chat.insert("\n"),
149 "backspace" if chat.cursor > 0 => {
150 let at = chat.byte(chat.cursor - 1);
151 chat.draft.remove(at);
152 chat.cursor -= 1;
153 },
154 "delete" if chat.cursor < chat.draft.chars().count() => {
155 let at = chat.byte(chat.cursor);
156 chat.draft.remove(at);
157 },
158 "left" => chat.cursor = chat.cursor.saturating_sub(1),
159 "right" => chat.cursor = (chat.cursor + 1).min(chat.draft.chars().count()),
160 "home" => chat.cursor = 0,
161 "end" => chat.cursor = chat.draft.chars().count(),
162 "paste" => chat.insert(key.text.as_deref().unwrap_or_default()),
163 _ => match key.typed() {
164 Some(text) => chat.insert(text),
165 None => continue,
166 },
167 }
168 },
169 Input::Msg(Msg::Send(text), _) => chat.send(text),
170 Input::Msg(Msg::Focus, _) => session.focus(sf, Some(EDITOR))?,
171 Input::Event(_) | Input::Clipboard(_) => continue,
172 }
173 session.render(sf, chat.view())?;
174 }
175 session.close()?;
176 println!("Chat closed after {} messages.", chat.transcript.len());
177 Ok(())
178}Sourcepub fn stylesheet(
&mut self,
sf: Surface,
name: &str,
css: Option<&str>,
) -> Result<(), Error>
pub fn stylesheet( &mut self, sf: Surface, name: &str, css: Option<&str>, ) -> Result<(), Error>
Sourcepub fn blobs(&mut self, ids: Vec<String>) -> Result<Vec<String>, Error>
pub fn blobs(&mut self, ids: Vec<String>) -> Result<Vec<String>, Error>
Asks which of ids Tern still holds. Input arriving meanwhile waits
for next.
§Errors
Error::Timeout without a reply in 2 s, or I/O.
Sourcepub fn waker(&self) -> Option<Waker>
pub fn waker(&self) -> Option<Waker>
The terminal’s waker: Waker::wake from any thread makes the
current or next next return Ok(None). None when
the terminal has no waker.
Sourcepub fn write_raw(&mut self, bytes: &[u8]) -> Result<(), Error>
pub fn write_raw(&mut self, bytes: &[u8]) -> Result<(), Error>
Writes bytes to the terminal as they are (OSC sequences, mode
switches), outside any surface.
§Errors
When the output fails.
Sourcepub fn next(
&mut self,
timeout: Option<Duration>,
) -> Result<Option<Input<M>>, Error>
pub fn next( &mut self, timeout: Option<Duration>, ) -> Result<Option<Input<M>>, Error>
Waits up to timeout (None: until something arrives) for the next
key, unhandled event, handler message, or clipboard packet.
Ok(None): the timeout passed or the waker was woken.
§Errors
Error::InputClosed at the end of input, or I/O.
Examples found in repository?
More examples
1121fn main() -> Result<(), tern_sdk::Error> {
1122 let mut dash = Dash::new();
1123 let Some(mut session) = Session::connect(Options::new().app("dashboard"))? else {
1124 print!("{}", dash.view(true).plain(100));
1125 return Ok(());
1126 };
1127 let sf = session.open(SurfaceOptions::screen().title("Fleet").font(Font::Sans))?;
1128 session.render(sf, dash.view(false))?;
1129 let mut next = Instant::now() + TICK;
1130 loop {
1131 let left = next.saturating_duration_since(Instant::now());
1132 match session.next(Some(left))? {
1133 None => {
1134 dash.tick();
1135 next = Instant::now() + TICK;
1136 },
1137 Some(Input::Msg(msg, _)) => dash.update(msg),
1138 Some(Input::Key(key)) => {
1139 if key.is("escape") && dash.confirming.is_some() {
1140 dash.update(Msg::Keep);
1141 } else if key.is("enter") && dash.confirming.is_some() {
1142 dash.update(Msg::Confirm);
1143 } else if key.is("escape") || key.is("q") || key.is("ctrl+c") {
1144 break;
1145 } else if key.is("tab") || key.is("]") {
1146 dash.page = dash.page.turn(false);
1147 } else if key.is("shift+tab") || key.is("[") {
1148 dash.page = dash.page.turn(true);
1149 } else if key.is("up") || key.is("k") {
1150 dash.pick(dash.page, -1);
1151 } else if key.is("down") || key.is("j") {
1152 dash.pick(dash.page, 1);
1153 } else if key.is("r") {
1154 dash.update(Msg::Refresh);
1155 } else if key.is("a") {
1156 dash.act_selected();
1157 } else if key.is("enter") {
1158 if let Some(id) = dash.selected[dash.page.index()] {
1159 dash.update(Msg::Open(dash.page, id.into()));
1160 }
1161 } else {
1162 continue;
1163 }
1164 },
1165 Some(Input::Event(_) | Input::Clipboard(_)) => continue,
1166 }
1167 session.render(sf, dash.view(false))?;
1168 }
1169 session.close()?;
1170 Ok(())
1171}128fn main() -> Result<(), tern_sdk::Error> {
129 let Some(mut session) = Session::connect(Options::new().feature(feature::SEND))? else {
130 return plain();
131 };
132 let sf = session.open(SurfaceOptions::inline().title("chat"))?;
133 let mut chat = Chat::default();
134 session.render(sf, chat.view())?;
135 session.focus(sf, Some(EDITOR))?;
136 while let Some(input) = session.next(None)? {
137 match input {
138 Input::Key(key) => {
139 if key.is("escape") || key.is("ctrl+c") || key.is("ctrl+d") {
140 break;
141 }
142 match key.name.as_str() {
143 "enter" if !key.shift && !key.alt => {
144 let text = std::mem::take(&mut chat.draft);
145 chat.cursor = 0;
146 chat.send(text);
147 },
148 "enter" => chat.insert("\n"),
149 "backspace" if chat.cursor > 0 => {
150 let at = chat.byte(chat.cursor - 1);
151 chat.draft.remove(at);
152 chat.cursor -= 1;
153 },
154 "delete" if chat.cursor < chat.draft.chars().count() => {
155 let at = chat.byte(chat.cursor);
156 chat.draft.remove(at);
157 },
158 "left" => chat.cursor = chat.cursor.saturating_sub(1),
159 "right" => chat.cursor = (chat.cursor + 1).min(chat.draft.chars().count()),
160 "home" => chat.cursor = 0,
161 "end" => chat.cursor = chat.draft.chars().count(),
162 "paste" => chat.insert(key.text.as_deref().unwrap_or_default()),
163 _ => match key.typed() {
164 Some(text) => chat.insert(text),
165 None => continue,
166 },
167 }
168 },
169 Input::Msg(Msg::Send(text), _) => chat.send(text),
170 Input::Msg(Msg::Focus, _) => session.focus(sf, Some(EDITOR))?,
171 Input::Event(_) | Input::Clipboard(_) => continue,
172 }
173 session.render(sf, chat.view())?;
174 }
175 session.close()?;
176 println!("Chat closed after {} messages.", chat.transcript.len());
177 Ok(())
178}Sourcepub fn pump(&mut self, timeout: Duration) -> Result<(), Error>
pub fn pump(&mut self, timeout: Duration) -> Result<(), Error>
Processes input for up to timeout without returning it: acks
release waiting frames; keys and events wait for
next.
§Errors
Error::InputClosed at the end of input, or I/O.
Sourcepub fn close(self) -> Result<(), Error>
pub fn close(self) -> Result<(), Error>
Closes the session: open surfaces (each as its options say), the input modes, a short drain of late input, and the tty.
§Errors
The first I/O error; every step still runs.
Examples found in repository?
101fn main() -> Result<(), tern_sdk::Error> {
102 let Some(mut session) = Session::connect(Options::new())? else {
103 for (i, step) in STEPS.iter().enumerate() {
104 println!("[{}/{}] {step}", i + 1, STEPS.len());
105 std::thread::sleep(STEP);
106 }
107 println!("Built demo.");
108 return Ok(());
109 };
110 let start = Instant::now();
111 let sf = session.open(SurfaceOptions::flow().title("progress"))?;
112 let mut build =
113 Build { log: String::new(), done: 0, took: None, status: Status::Running };
114 session.render(sf, view(&build))?;
115 for step in STEPS {
116 if wait(&mut session, STEP)? {
117 build.status = Status::Cancelled;
118 break;
119 }
120 build.log.push_str("\x1b[32m✓\x1b[0m ");
121 build.log.push_str(step);
122 build.log.push_str("\r\n");
123 build.done += 1;
124 session.render(sf, view(&build))?;
125 }
126 if build.status == Status::Running {
127 build.status = Status::Done;
128 }
129 let took = start.elapsed();
130 build.took = Some(took.as_millis() as u64);
131 session.render(sf, view(&build))?;
132 session.close()?;
133 match build.status {
134 Status::Done => println!("Built demo in {:.1}s.", took.as_secs_f64()),
135 _ => println!("Build cancelled."),
136 }
137 Ok(())
138}More examples
1121fn main() -> Result<(), tern_sdk::Error> {
1122 let mut dash = Dash::new();
1123 let Some(mut session) = Session::connect(Options::new().app("dashboard"))? else {
1124 print!("{}", dash.view(true).plain(100));
1125 return Ok(());
1126 };
1127 let sf = session.open(SurfaceOptions::screen().title("Fleet").font(Font::Sans))?;
1128 session.render(sf, dash.view(false))?;
1129 let mut next = Instant::now() + TICK;
1130 loop {
1131 let left = next.saturating_duration_since(Instant::now());
1132 match session.next(Some(left))? {
1133 None => {
1134 dash.tick();
1135 next = Instant::now() + TICK;
1136 },
1137 Some(Input::Msg(msg, _)) => dash.update(msg),
1138 Some(Input::Key(key)) => {
1139 if key.is("escape") && dash.confirming.is_some() {
1140 dash.update(Msg::Keep);
1141 } else if key.is("enter") && dash.confirming.is_some() {
1142 dash.update(Msg::Confirm);
1143 } else if key.is("escape") || key.is("q") || key.is("ctrl+c") {
1144 break;
1145 } else if key.is("tab") || key.is("]") {
1146 dash.page = dash.page.turn(false);
1147 } else if key.is("shift+tab") || key.is("[") {
1148 dash.page = dash.page.turn(true);
1149 } else if key.is("up") || key.is("k") {
1150 dash.pick(dash.page, -1);
1151 } else if key.is("down") || key.is("j") {
1152 dash.pick(dash.page, 1);
1153 } else if key.is("r") {
1154 dash.update(Msg::Refresh);
1155 } else if key.is("a") {
1156 dash.act_selected();
1157 } else if key.is("enter") {
1158 if let Some(id) = dash.selected[dash.page.index()] {
1159 dash.update(Msg::Open(dash.page, id.into()));
1160 }
1161 } else {
1162 continue;
1163 }
1164 },
1165 Some(Input::Event(_) | Input::Clipboard(_)) => continue,
1166 }
1167 session.render(sf, dash.view(false))?;
1168 }
1169 session.close()?;
1170 Ok(())
1171}128fn main() -> Result<(), tern_sdk::Error> {
129 let Some(mut session) = Session::connect(Options::new().feature(feature::SEND))? else {
130 return plain();
131 };
132 let sf = session.open(SurfaceOptions::inline().title("chat"))?;
133 let mut chat = Chat::default();
134 session.render(sf, chat.view())?;
135 session.focus(sf, Some(EDITOR))?;
136 while let Some(input) = session.next(None)? {
137 match input {
138 Input::Key(key) => {
139 if key.is("escape") || key.is("ctrl+c") || key.is("ctrl+d") {
140 break;
141 }
142 match key.name.as_str() {
143 "enter" if !key.shift && !key.alt => {
144 let text = std::mem::take(&mut chat.draft);
145 chat.cursor = 0;
146 chat.send(text);
147 },
148 "enter" => chat.insert("\n"),
149 "backspace" if chat.cursor > 0 => {
150 let at = chat.byte(chat.cursor - 1);
151 chat.draft.remove(at);
152 chat.cursor -= 1;
153 },
154 "delete" if chat.cursor < chat.draft.chars().count() => {
155 let at = chat.byte(chat.cursor);
156 chat.draft.remove(at);
157 },
158 "left" => chat.cursor = chat.cursor.saturating_sub(1),
159 "right" => chat.cursor = (chat.cursor + 1).min(chat.draft.chars().count()),
160 "home" => chat.cursor = 0,
161 "end" => chat.cursor = chat.draft.chars().count(),
162 "paste" => chat.insert(key.text.as_deref().unwrap_or_default()),
163 _ => match key.typed() {
164 Some(text) => chat.insert(text),
165 None => continue,
166 },
167 }
168 },
169 Input::Msg(Msg::Send(text), _) => chat.send(text),
170 Input::Msg(Msg::Focus, _) => session.focus(sf, Some(EDITOR))?,
171 Input::Event(_) | Input::Clipboard(_) => continue,
172 }
173 session.render(sf, chat.view())?;
174 }
175 session.close()?;
176 println!("Chat closed after {} messages.", chat.transcript.len());
177 Ok(())
178}