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 tmux, screen or zellij, or the terminal answers DA1 first or not
in time).
§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
127fn main() -> Result<(), tern_sdk::Error> {
128 let Some(mut session) = Session::connect(Options::new().feature(feature::SEND))? else {
129 println!("chat needs a terminal that speaks TSP (Tern).");
130 return Ok(());
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(_) => 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
127fn main() -> Result<(), tern_sdk::Error> {
128 let Some(mut session) = Session::connect(Options::new().feature(feature::SEND))? else {
129 println!("chat needs a terminal that speaks TSP (Tern).");
130 return Ok(());
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(_) => 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
127fn main() -> Result<(), tern_sdk::Error> {
128 let Some(mut session) = Session::connect(Options::new().feature(feature::SEND))? else {
129 println!("chat needs a terminal that speaks TSP (Tern).");
130 return Ok(());
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(_) => 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?
127fn main() -> Result<(), tern_sdk::Error> {
128 let Some(mut session) = Session::connect(Options::new().feature(feature::SEND))? else {
129 println!("chat needs a terminal that speaks TSP (Tern).");
130 return Ok(());
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(_) => 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 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, or handler message. Ok(None) when the time is
up.
§Errors
Error::InputClosed at the end of input, or I/O.
Examples found in repository?
More examples
127fn main() -> Result<(), tern_sdk::Error> {
128 let Some(mut session) = Session::connect(Options::new().feature(feature::SEND))? else {
129 println!("chat needs a terminal that speaks TSP (Tern).");
130 return Ok(());
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(_) => 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
127fn main() -> Result<(), tern_sdk::Error> {
128 let Some(mut session) = Session::connect(Options::new().feature(feature::SEND))? else {
129 println!("chat needs a terminal that speaks TSP (Tern).");
130 return Ok(());
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(_) => continue,
172 }
173 session.render(sf, chat.view())?;
174 }
175 session.close()?;
176 println!("Chat closed after {} messages.", chat.transcript.len());
177 Ok(())
178}