Skip to main content

Session

Struct Session 

Source
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>

Source

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?
examples/progress.rs (line 102)
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
Hide additional examples
examples/dashboard.rs (line 1123)
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}
examples/chat.rs (line 129)
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}
Source

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.

Source

pub const fn caps(&self) -> &Capabilities

What the terminal supports.

Source

pub fn open(&mut self, options: SurfaceOptions) -> Result<Surface, Error>

Opens a surface.

§Errors

When writing fails.

Examples found in repository?
examples/progress.rs (line 111)
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
Hide additional examples
examples/dashboard.rs (line 1127)
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}
examples/chat.rs (line 132)
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}
Source

pub fn surface_id(&self, sf: Surface) -> &str

The id of sf.

§Panics

When sf comes from another session.

Source

pub fn is_open(&self, sf: Surface) -> bool

Whether sf is still open (not closed, and not gone).

§Panics

When sf comes from another session.

Source

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?
examples/progress.rs (line 114)
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
Hide additional examples
examples/dashboard.rs (line 1128)
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}
examples/chat.rs (line 134)
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}
Source

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.

Source

pub fn focus(&mut self, sf: Surface, id: Option<&str>) -> Result<(), Error>

Moves the caret to field id, or nowhere.

§Errors

As send.

Examples found in repository?
examples/chat.rs (line 135)
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}
Source

pub fn reveal( &mut self, sf: Surface, id: &str, at: RevealAt, ) -> Result<(), Error>

Scrolls node id into view.

§Errors

As send.

Source

pub fn scroll( &mut self, sf: Surface, id: &str, by: ScrollBy, ) -> Result<(), Error>

Scrolls the container around node id.

§Errors

As send.

Source

pub fn settle(&mut self, sf: Surface, id: &str) -> Result<(), Error>

Hints that node id won’t change soon.

§Errors

As send.

Source

pub fn suspend(&mut self, sf: Surface) -> Result<(), Error>

Hands the pane back to the grid.

§Errors

As send.

Source

pub fn resume(&mut self, sf: Surface) -> Result<(), Error>

Takes the pane again after suspend.

§Errors

As send.

Source

pub fn stylesheet( &mut self, sf: Surface, name: &str, css: Option<&str>, ) -> Result<(), Error>

Installs or replaces stylesheet name of sf; None removes it.

§Errors

A closed surface, or I/O.

Source

pub fn close_surface(&mut self, sf: Surface, keep: bool) -> Result<(), Error>

Closes sf: sends what still waits (credits or not), then x.

§Errors

A closed surface, or I/O.

Source

pub fn blob( &mut self, data: Vec<u8>, mime: Option<&str>, ) -> Result<String, Error>

Sends a blob once per session and returns its id.

§Errors

Error::BlobTooLarge over 16 MiB, or I/O.

Source

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.

Source

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.

Source

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.

Source

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?
examples/progress.rs (line 93)
89fn wait(session: &mut Session, time: Duration) -> Result<bool, tern_sdk::Error> {
90	let until = Instant::now() + time;
91	loop {
92		let left = until.saturating_duration_since(Instant::now());
93		match session.next(Some(left))? {
94			None => return Ok(false),
95			Some(Input::Key(key)) if key.is("ctrl+c") => return Ok(true),
96			Some(_) => {},
97		}
98	}
99}
More examples
Hide additional examples
examples/dashboard.rs (line 1132)
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}
examples/chat.rs (line 136)
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}
Source

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.

Source

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?
examples/progress.rs (line 132)
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
Hide additional examples
examples/dashboard.rs (line 1169)
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}
examples/chat.rs (line 175)
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}

Trait Implementations§

Source§

impl<M> Drop for Session<M>

Source§

fn drop(&mut self)

Executes the destructor for this type. Read more
Source§

fn pin_drop(self: Pin<&mut Self>)

🔬This is a nightly-only experimental API. (pin_ergonomics)
Execute the destructor for this type, but different to Drop::drop, it requires self to be pinned. Read more

Auto Trait Implementations§

§

impl<M = ()> !RefUnwindSafe for Session<M>

§

impl<M = ()> !Send for Session<M>

§

impl<M = ()> !Sync for Session<M>

§

impl<M = ()> !UnwindSafe for Session<M>

§

impl<M> Freeze for Session<M>
where Vec<State<M>>: Freeze, VecDeque<Input<M>>: Freeze,

§

impl<M> Unpin for Session<M>
where Vec<State<M>>: Unpin, VecDeque<Input<M>>: Unpin,

§

impl<M> UnsafeUnpin for Session<M>
where Vec<State<M>>: UnsafeUnpin, VecDeque<Input<M>>: UnsafeUnpin,

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more