1use std::{
41 collections::{HashSet, VecDeque},
42 path::PathBuf,
43 time::{Duration, Instant},
44};
45
46use serde_json::Value;
47
48#[cfg(any(unix, windows))]
49use crate::term::Tty;
50use crate::{
51 Error,
52 input::{Item, Parser},
53 keys::{Decoder, Key},
54 reconcile::{Doc, Routes},
55 record::Recorder,
56 term::{self, Terminal},
57 ui::View,
58 wire::{
59 Blob, Cell, Close, Encoder, Event, Frame, Message, Mode, Op, Open, Palette, Query, Reply,
60 RevealAt, ScrollBy, Sheet,
61 },
62};
63
64const FLUSH_AFTER: Duration = Duration::from_millis(30);
66const DRAIN: Duration = Duration::from_millis(50);
68const QUERY_TIMEOUT: Duration = Duration::from_secs(2);
70
71#[derive(Clone, Debug)]
73pub struct Options {
74 pub app: Option<String>,
76 pub version: Option<String>,
78 pub features: Vec<String>,
80 pub timeout: Duration,
82 pub bracketed_paste: bool,
84 pub kitty_keyboard: bool,
86 pub record: Option<PathBuf>,
88}
89
90impl Default for Options {
91 fn default() -> Self {
92 Self {
93 app: None,
94 version: None,
95 features: Vec::new(),
96 timeout: Duration::from_secs(1),
97 bracketed_paste: true,
98 kitty_keyboard: true,
99 record: std::env::var_os("TERN_TSP_RECORD")
100 .filter(|v| !v.is_empty())
101 .map(PathBuf::from),
102 }
103 }
104}
105
106impl Options {
107 pub fn new() -> Self {
109 Self::default()
110 }
111
112 pub fn app(mut self, app: impl Into<String>) -> Self {
114 self.app = Some(app.into());
115 self
116 }
117
118 pub fn version(mut self, version: impl Into<String>) -> Self {
120 self.version = Some(version.into());
121 self
122 }
123
124 pub fn feature(mut self, feature: impl Into<String>) -> Self {
126 self.features.push(feature.into());
127 self
128 }
129
130 pub const fn timeout(mut self, timeout: Duration) -> Self {
132 self.timeout = timeout;
133 self
134 }
135
136 pub const fn bracketed_paste(mut self, on: bool) -> Self {
138 self.bracketed_paste = on;
139 self
140 }
141
142 pub const fn kitty_keyboard(mut self, on: bool) -> Self {
144 self.kitty_keyboard = on;
145 self
146 }
147
148 pub fn record(mut self, path: Option<PathBuf>) -> Self {
150 self.record = path;
151 self
152 }
153}
154
155#[derive(Clone, Debug, Default, PartialEq)]
158pub struct Capabilities {
159 pub term: String,
161 pub version: String,
163 pub kinds: Vec<String>,
165 pub features: Vec<String>,
167 pub apc: usize,
169 pub credits: u32,
171 pub cols: u32,
173 pub cell: Option<Cell>,
175 pub dark: bool,
177 pub reduce_motion: bool,
179 pub hour12: bool,
182}
183
184impl Capabilities {
185 pub fn has_kind(&self, kind: &str) -> bool {
187 self.kinds.iter().any(|k| k == kind)
188 }
189
190 pub fn has_feature(&self, feature: &str) -> bool {
192 self.features.iter().any(|f| f == feature)
193 }
194}
195
196#[derive(Clone, Debug)]
198pub struct SurfaceOptions {
199 pub id: Option<String>,
201 pub mode: Mode,
203 pub title: Option<String>,
205 pub role: Option<String>,
207 pub listen: bool,
209 pub adopt: bool,
211 pub keep: bool,
213}
214
215impl Default for SurfaceOptions {
216 fn default() -> Self {
217 Self {
218 id: None,
219 mode: Mode::default(),
220 title: None,
221 role: None,
222 listen: true,
223 adopt: false,
224 keep: true,
225 }
226 }
227}
228
229impl SurfaceOptions {
230 pub fn new(mode: Mode) -> Self {
232 Self { mode, ..Self::default() }
233 }
234
235 pub fn inline() -> Self {
237 Self::new(Mode::Inline)
238 }
239
240 pub fn screen() -> Self {
242 Self::new(Mode::Screen)
243 }
244
245 pub fn flow() -> Self {
247 Self::new(Mode::Flow)
248 }
249
250 pub fn id(mut self, id: impl Into<String>) -> Self {
252 self.id = Some(id.into());
253 self
254 }
255
256 pub fn title(mut self, title: impl Into<String>) -> Self {
258 self.title = Some(title.into());
259 self
260 }
261
262 pub fn role(mut self, role: impl Into<String>) -> Self {
264 self.role = Some(role.into());
265 self
266 }
267
268 pub const fn listen(mut self, listen: bool) -> Self {
270 self.listen = listen;
271 self
272 }
273
274 pub const fn adopt(mut self, adopt: bool) -> Self {
276 self.adopt = adopt;
277 self
278 }
279
280 pub const fn keep(mut self, keep: bool) -> Self {
282 self.keep = keep;
283 self
284 }
285}
286
287#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
290pub struct Surface(usize);
291
292#[derive(Debug)]
294pub enum Input<M> {
295 Key(Key),
297 Event(Event),
299 Msg(M, Event),
301}
302
303struct State<M> {
305 id: String,
307 listen: bool,
309 keep: bool,
311 open: bool,
313 seq: u64,
315 acked: u64,
317 sent: Doc,
319 reset_main: bool,
322 pending: Option<Doc>,
324 queued: Vec<Op>,
326 routes: Routes<M>,
328}
329
330impl<M> State<M> {
331 const fn blocked(&self, credits: u32) -> bool {
333 self.listen && self.seq.saturating_sub(self.acked) >= credits as u64
334 }
335}
336
337struct Out {
339 term: Box<dyn Terminal>,
341 enc: Encoder,
343 record: Option<Recorder>,
345}
346
347impl Out {
348 fn send(&mut self, message: &Message) -> Result<(), Error> {
350 let bytes = self.enc.encode(message)?;
351 if let Some(record) = &mut self.record {
352 let body = match message {
353 Message::Blob(_) => Value::String(message.body()?),
354 _ => serde_json::from_str(&message.body()?)?,
355 };
356 record.log("out", message.verb(), &message.params(), body);
357 }
358 self.term.write(&bytes)?;
359 Ok(())
360 }
361
362 fn flush<M>(&mut self, state: &mut State<M>) -> Result<(), Error> {
365 let mut ops = state
366 .pending
367 .as_ref()
368 .map_or_else(Vec::new, |doc| state.sent.ops(doc, &state.id));
369 if state.reset_main && (state.pending.is_some() || !state.queued.is_empty()) {
370 ops.insert(0, Op::Del { id: "main".into() });
371 }
372 let queued_at = ops.len();
373 ops.append(&mut state.queued);
374 if !ops.is_empty() {
375 let mut message = Message::Frame(Frame { sf: state.id.clone(), s: state.seq + 1, ops });
376 if let Err(err) = self.send(&message) {
377 if let Message::Frame(frame) = &mut message {
378 state.queued = frame.ops.split_off(queued_at);
379 }
380 return Err(err);
381 }
382 state.seq += 1;
383 state.reset_main = false;
384 }
385 if let Some(doc) = state.pending.take() {
386 state.sent = doc;
387 }
388 Ok(())
389 }
390}
391
392pub struct Session<M = ()> {
395 out: Out,
397 parser: Parser,
399 keys: Decoder,
401 caps: Capabilities,
403 surfaces: Vec<State<M>>,
405 next_id: u32,
407 inbox: VecDeque<Input<M>>,
409 replies: VecDeque<Reply>,
411 blobs: HashSet<String>,
413 modes: (bool, bool),
415 last_in: Instant,
417 closed: bool,
419}
420
421impl<M> Session<M> {
422 #[cfg(any(unix, windows))]
430 pub fn connect(options: Options) -> Result<Option<Self>, Error> {
431 if !available() {
432 return Ok(None);
433 }
434 let tty = Tty::open()?;
435 Self::with_terminal(tty, options)
436 }
437
438 pub fn with_terminal(
445 term: impl Terminal + 'static,
446 options: Options,
447 ) -> Result<Option<Self>, Error> {
448 let record = options.record.as_deref().map(Recorder::open).transpose()?;
449 let mut session = Self {
450 out: Out { term: Box::new(term), enc: Encoder::default(), record },
451 parser: Parser::new(),
452 keys: Decoder::new(),
453 caps: Capabilities::default(),
454 surfaces: Vec::new(),
455 next_id: 1,
456 inbox: VecDeque::new(),
457 replies: VecDeque::new(),
458 blobs: HashSet::new(),
459 modes: (false, false),
460 last_in: Instant::now(),
461 closed: false,
462 };
463 let app = options.app.clone().or_else(exe_name);
464 match session.handshake(app, &options) {
465 Ok(true) => {},
466 Ok(false) => {
467 session.closed = true;
468 session.out.term.restore()?;
469 return Ok(None);
470 },
471 Err(err) => {
472 session.closed = true;
473 let _ = session.out.term.restore();
474 return Err(err);
475 },
476 }
477 let (paste, kitty) = (options.bracketed_paste, options.kitty_keyboard);
478 let mut enable = Vec::new();
479 if paste {
480 enable.extend_from_slice(b"\x1b[?2004h");
481 }
482 if kitty {
483 enable.extend_from_slice(b"\x1b[>1u");
484 }
485 session.modes = (paste, kitty);
486 session.out.term.modes(paste, kitty);
487 if !enable.is_empty() {
488 session.out.term.write(&enable)?;
489 }
490 Ok(Some(session))
491 }
492
493 fn handshake(&mut self, app: Option<String>, options: &Options) -> Result<bool, Error> {
495 let hello =
496 Query::Hello { app, ver: options.version.clone(), features: options.features.clone() };
497 let mut bytes = self.out.enc.encode(&Message::Query(hello.clone()))?;
498 bytes.extend_from_slice(b"\x1b[c");
499 if let Some(record) = &mut self.out.record {
500 record.log("out", "q", &[], serde_json::to_value(&hello)?);
501 }
502 self.out.term.write(&bytes)?;
503 let deadline = Instant::now() + options.timeout;
504 let mut buf = [0u8; 4096];
505 loop {
506 let left = deadline.saturating_duration_since(Instant::now());
507 if left.is_zero() {
508 return Ok(false);
509 }
510 let Some(n) = self.out.term.read(&mut buf, Some(left))? else {
511 continue;
512 };
513 if n == 0 {
514 return Ok(false);
515 }
516 self.last_in = Instant::now();
517 let mut accepted = false;
518 for item in self.parser.feed(&buf[..n]) {
519 match item {
520 Item::Reply(Reply::Hello(hello)) if !accepted => {
521 self.record_in("r", hello.raw.clone());
522 self.caps = Capabilities {
523 term: hello.term,
524 version: hello.ver,
525 kinds: hello.kinds,
526 features: hello.features,
527 apc: hello.apc,
528 credits: hello.credits,
529 cols: hello.cols,
530 cell: hello.cell,
531 dark: hello.dark,
532 reduce_motion: hello.reduce_motion,
533 hour12: hello.hour12,
534 };
535 self.out.enc.set_limit(self.caps.apc);
536 accepted = true;
537 },
538 Item::Da1 if !accepted => return Ok(false),
539 other => self.item(other)?,
540 }
541 }
542 if accepted {
543 return Ok(true);
544 }
545 }
546 }
547
548 pub const fn caps(&self) -> &Capabilities {
550 &self.caps
551 }
552
553 pub fn open(&mut self, options: SurfaceOptions) -> Result<Surface, Error> {
558 let id = options.id.clone().unwrap_or_else(|| {
559 let id = format!("s{}", self.next_id);
560 self.next_id += 1;
561 id
562 });
563 self.out.send(&Message::Open(Open {
564 id: id.clone(),
565 mode: Some(options.mode),
566 title: options.title,
567 role: options.role,
568 listen: (!options.listen).then_some(false),
569 adopt: options.adopt.then_some(true),
570 }))?;
571 let previous = options
572 .adopt
573 .then(|| {
574 self
575 .surfaces
576 .iter()
577 .rposition(|state| state.id == id && !state.open)
578 })
579 .flatten();
580 let (sent, seq) = previous.map_or_else(
581 || (Doc::new(), 0),
582 |i| {
583 let state = &mut self.surfaces[i];
584 (std::mem::take(&mut state.sent), state.seq)
585 },
586 );
587 self.surfaces.push(State {
588 id,
589 listen: options.listen,
590 keep: options.keep,
591 open: true,
592 seq,
593 acked: seq,
594 sent,
595 reset_main: options.adopt && previous.is_none(),
596 pending: None,
597 queued: Vec::new(),
598 routes: Routes::default(),
599 });
600 Ok(Surface(self.surfaces.len() - 1))
601 }
602
603 pub fn surface_id(&self, sf: Surface) -> &str {
608 &self.surfaces[sf.0].id
609 }
610
611 pub fn is_open(&self, sf: Surface) -> bool {
616 self.surfaces[sf.0].open
617 }
618
619 pub fn render(&mut self, sf: Surface, view: impl Into<View<M>>) -> Result<(), Error> {
627 let (doc, routes) = Doc::build(view.into())?;
628 let credits = self.caps.credits;
629 let state = open(&mut self.surfaces, sf)?;
630 state.routes = routes;
631 state.pending = Some(doc);
632 if state.blocked(credits) {
633 return self.pump(Duration::ZERO);
635 }
636 self.out.flush(state)
637 }
638
639 pub fn send(&mut self, sf: Surface, ops: Vec<Op>) -> Result<(), Error> {
645 let credits = self.caps.credits;
646 let state = open(&mut self.surfaces, sf)?;
647 state.queued.extend(ops);
648 if state.blocked(credits) {
649 return Ok(());
650 }
651 self.out.flush(state)
652 }
653
654 pub fn focus(&mut self, sf: Surface, id: Option<&str>) -> Result<(), Error> {
659 self.send(sf, vec![Op::Focus { id: id.map(str::to_owned) }])
660 }
661
662 pub fn reveal(&mut self, sf: Surface, id: &str, at: RevealAt) -> Result<(), Error> {
667 self.send(sf, vec![Op::Reveal { id: id.to_owned(), at }])
668 }
669
670 pub fn scroll(&mut self, sf: Surface, id: &str, by: ScrollBy) -> Result<(), Error> {
675 self.send(sf, vec![Op::Scroll { id: id.to_owned(), by }])
676 }
677
678 pub fn settle(&mut self, sf: Surface, id: &str) -> Result<(), Error> {
683 self.send(sf, vec![Op::Settle { id: id.to_owned() }])
684 }
685
686 pub fn suspend(&mut self, sf: Surface) -> Result<(), Error> {
691 self.send(sf, vec![Op::Suspend])
692 }
693
694 pub fn resume(&mut self, sf: Surface) -> Result<(), Error> {
699 self.send(sf, vec![Op::Resume])
700 }
701
702 pub fn stylesheet(&mut self, sf: Surface, name: &str, css: Option<&str>) -> Result<(), Error> {
707 let id = open(&mut self.surfaces, sf)?.id.clone();
708 self.out.send(&Message::Sheet(Sheet {
709 sf: Some(id),
710 name: name.to_owned(),
711 css: css.map(str::to_owned),
712 }))
713 }
714
715 pub fn palette(&mut self, sf: Surface, palette: Palette) -> Result<(), Error> {
720 let id = open(&mut self.surfaces, sf)?.id.clone();
721 self
722 .out
723 .send(&Message::Palette(Palette { sf: Some(id), ..palette }))
724 }
725
726 pub fn close_surface(&mut self, sf: Surface, keep: bool) -> Result<(), Error> {
731 let state = open(&mut self.surfaces, sf)?;
732 let frame = self.out.flush(state);
733 let id = state.id.clone();
734 let close = self.out.send(&Message::Close(Close { id, keep }));
735 if close.is_ok() {
736 state.open = false;
737 state.pending = None;
738 state.queued.clear();
739 if keep {
740 state.sent.retain_main();
741 } else {
742 state.sent = Doc::new();
743 }
744 }
745 frame.and(close)
746 }
747
748 pub fn blob(&mut self, data: Vec<u8>, mime: Option<&str>) -> Result<String, Error> {
753 let blob = Blob::new(data, mime.map(str::to_owned))?;
754 let id = blob.id.clone();
755 if !self.blobs.contains(&id) {
756 self.out.send(&Message::Blob(blob))?;
757 self.blobs.insert(id.clone());
758 }
759 Ok(id)
760 }
761
762 pub fn blobs(&mut self, ids: Vec<String>) -> Result<Vec<String>, Error> {
768 self.out.send(&Message::Query(Query::Blobs { ids }))?;
769 let deadline = Instant::now() + QUERY_TIMEOUT;
770 loop {
771 if let Some(at) = self
772 .replies
773 .iter()
774 .position(|r| matches!(r, Reply::Blobs(_)))
775 && let Some(Reply::Blobs(reply)) = self.replies.remove(at)
776 {
777 return Ok(reply.have);
778 }
779 let left = deadline.saturating_duration_since(Instant::now());
780 if left.is_zero() {
781 return Err(Error::Timeout("the blobs reply"));
782 }
783 self.read(Some(left))?;
784 }
785 }
786
787 pub fn next(&mut self, timeout: Option<Duration>) -> Result<Option<Input<M>>, Error> {
794 let deadline = timeout.map(|t| Instant::now() + t);
795 loop {
796 if let Some(input) = self.inbox.pop_front() {
797 return Ok(Some(input));
798 }
799 if deadline.is_some_and(|d| Instant::now() >= d) && !self.read(Some(Duration::ZERO))? {
800 return Ok(self.inbox.pop_front());
801 }
802 let left = deadline.map(|d| d.saturating_duration_since(Instant::now()));
803 self.read(left)?;
804 }
805 }
806
807 pub fn pump(&mut self, timeout: Duration) -> Result<(), Error> {
814 let deadline = Instant::now() + timeout;
815 loop {
816 let left = deadline.saturating_duration_since(Instant::now());
817 if !self.read(Some(left))? && Instant::now() >= deadline {
818 return Ok(());
819 }
820 }
821 }
822
823 fn read(&mut self, timeout: Option<Duration>) -> Result<bool, Error> {
826 let held = self.parser.pending() || self.keys.pending();
827 let flush_in = held.then(|| FLUSH_AFTER.saturating_sub(self.last_in.elapsed()));
828 let wait = match (timeout, flush_in) {
829 (Some(t), Some(f)) => Some(t.min(f)),
830 (t, f) => t.or(f),
831 };
832 let mut buf = [0u8; 8192];
833 match self.out.term.read(&mut buf, wait)? {
834 Some(0) => Err(Error::InputClosed),
835 Some(n) => {
836 self.last_in = Instant::now();
837 for item in self.parser.feed(&buf[..n]) {
838 self.item(item)?;
839 }
840 Ok(true)
841 },
842 None => {
843 if held && self.last_in.elapsed() >= FLUSH_AFTER {
844 for item in self.parser.flush() {
845 self.item(item)?;
846 }
847 for key in self.keys.flush() {
848 self.inbox.push_back(Input::Key(key));
849 }
850 return Ok(true);
851 }
852 Ok(false)
853 },
854 }
855 }
856
857 fn item(&mut self, item: Item) -> Result<(), Error> {
859 match item {
860 Item::Keys(bytes) => {
861 for key in self.keys.feed(&bytes) {
862 self.inbox.push_back(Input::Key(key));
863 }
864 },
865 Item::Reply(reply) => {
866 self.record_in("r", reply.raw().clone());
867 self.replies.push_back(reply);
868 },
869 Item::Event(event) => {
870 self.record_in("e", event.raw().clone());
871 self.event(event)?;
872 },
873 Item::Da1 => {},
874 }
875 Ok(())
876 }
877
878 fn record_in(&mut self, verb: &str, raw: serde_json::Map<String, Value>) {
880 if let Some(record) = &mut self.out.record {
881 record.log("in", verb, &[], Value::Object(raw));
882 }
883 }
884
885 fn by_id(&self, sf: &str) -> Option<usize> {
887 self.surfaces.iter().rposition(|s| s.id == sf)
888 }
889
890 fn event(&mut self, event: Event) -> Result<(), Error> {
892 match &event {
893 Event::Ack(ack) => {
894 if let Some(i) = self.by_id(&ack.sf) {
895 let credits = self.caps.credits;
896 let state = &mut self.surfaces[i];
897 state.acked = state.acked.max(ack.s);
898 if state.open && !state.blocked(credits) {
899 self.out.flush(state)?;
900 }
901 }
902 return Ok(());
903 },
904 Event::Resize(r) => {
905 self.caps.cols = r.cols;
906 if r.cell.is_some() {
907 self.caps.cell = r.cell;
908 }
909 },
910 Event::Theme(t) => self.caps.dark = t.dark,
911 Event::Motion(m) => self.caps.reduce_motion = m.reduce,
912 Event::Gone(g) => {
913 for state in &mut self.surfaces {
914 if g.ids.contains(&state.id) && g.sf.as_ref().is_none_or(|sf| *sf == state.id) {
915 state.open = false;
916 }
917 }
918 },
919 _ => {},
920 }
921 let msg = event
922 .sf()
923 .and_then(|sf| self.by_id(sf))
924 .and_then(|i| self.surfaces[i].routes.route(&event));
925 self.inbox.push_back(match msg {
926 Some(msg) => Input::Msg(msg, event),
927 None => Input::Event(event),
928 });
929 Ok(())
930 }
931
932 pub fn close(mut self) -> Result<(), Error> {
938 self.shutdown()
939 }
940
941 fn shutdown(&mut self) -> Result<(), Error> {
943 if self.closed {
944 return Ok(());
945 }
946 self.closed = true;
947 let mut first: Option<Error> = None;
948 let mut note = |r: Result<(), Error>| {
949 if let Err(err) = r
950 && first.is_none()
951 {
952 first = Some(err);
953 }
954 };
955 for i in 0..self.surfaces.len() {
956 if self.surfaces[i].open {
957 let keep = self.surfaces[i].keep;
958 note(self.close_surface(Surface(i), keep));
959 }
960 }
961 let reset = term::mode_reset(self.modes.0, self.modes.1);
962 if !reset.is_empty() {
963 note(self.out.term.write(reset).map_err(Error::from));
964 }
965 self.out.term.modes(false, false);
966 let until = Instant::now() + DRAIN;
967 let mut buf = [0u8; 4096];
968 loop {
969 let left = until.saturating_duration_since(Instant::now());
970 if left.is_zero() {
971 break;
972 }
973 match self.out.term.read(&mut buf, Some(left)) {
974 Ok(Some(n)) if n > 0 => {},
975 _ => break,
976 }
977 }
978 note(self.out.term.restore().map_err(Error::from));
979 first.map_or(Ok(()), Err)
980 }
981}
982
983impl<M> Drop for Session<M> {
984 fn drop(&mut self) {
985 if let Err(err) = self.shutdown() {
986 tracing::warn!(%err, "closing the TSP session failed");
987 }
988 }
989}
990
991fn open<M>(surfaces: &mut [State<M>], sf: Surface) -> Result<&mut State<M>, Error> {
993 let state = &mut surfaces[sf.0];
994 if state.open {
995 Ok(state)
996 } else {
997 Err(Error::SurfaceClosed(state.id.clone()))
998 }
999}
1000
1001#[cfg(any(unix, windows))]
1003fn available() -> bool {
1004 let set = |name: &str| std::env::var_os(name).is_some_and(|v| !v.is_empty());
1005 if std::env::var_os("TERN_TSP").is_some_and(|v| v == "0") {
1006 return false;
1007 }
1008 if set("TMUX") || set("STY") || set("ZELLIJ") {
1009 return false;
1010 }
1011 term::is_tty()
1012}
1013
1014fn exe_name() -> Option<String> {
1016 let exe = std::env::current_exe().ok()?;
1017 Some(exe.file_stem()?.to_string_lossy().into_owned())
1018}