pub struct SurfaceOptions {
pub id: Option<String>,
pub mode: Mode,
pub title: Option<String>,
pub role: Option<String>,
pub listen: bool,
pub adopt: bool,
pub keep: bool,
pub closable: bool,
pub font: Option<Font>,
}Expand description
How to open a surface.
Fields§
§id: Option<String>The id (default s1, s2, … per session).
mode: ModeThe mode.
title: Option<String>Names the pane until the program sets its own title.
role: Option<String>data-surface for stylesheets.
listen: boolWhether the program reads its events (default true).
adopt: boolReopen a closed inline surface with this id.
keep: boolKeep it on screen when the session closes it (default true).
closable: boolClosing the pane loses nothing, so Tern closes it without asking
(default false).
font: Option<Font>The base face (default mono; Font::Sans for apps).
Implementations§
Source§impl SurfaceOptions
impl SurfaceOptions
Sourcepub fn inline() -> Self
pub fn inline() -> Self
An inline surface (a session program).
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 screen() -> Self
pub fn screen() -> Self
A screen surface (a full-screen view).
Examples found in repository?
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}Sourcepub fn flow() -> Self
pub fn flow() -> Self
A flow surface (command output that stays in the scrollback).
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}Sourcepub fn title(self, title: impl Into<String>) -> Self
pub fn title(self, title: impl Into<String>) -> Self
Sets the title.
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 const fn listen(self, listen: bool) -> Self
pub const fn listen(self, listen: bool) -> Self
Sets whether the program listens (no acks or events when not).
Sourcepub const fn keep(self, keep: bool) -> Self
pub const fn keep(self, keep: bool) -> Self
Sets whether the session’s close keeps the surface on screen.
Sourcepub const fn closable(self, closable: bool) -> Self
pub const fn closable(self, closable: bool) -> Self
Sets whether closing the pane skips Tern’s “a program is running” prompt.
Sourcepub const fn font(self, font: Font) -> Self
pub const fn font(self, font: Font) -> Self
Sets the base face: Font::Sans draws people’s text in the UI face
(machine strings stay mono).
Examples found in repository?
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}