aboutsummaryrefslogtreecommitdiff
path: root/include/ricochet/tui/session.hh
blob: 94cae688424b84a7108182491675056820fa24b9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
/*
 * Ricochet - A minimalist CLI web browser for fast, distraction-free navigation.
 * Copyright (C) 2026  Richard Qin
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

#pragma once

#include "ricochet/tui/input.hh"
#include "ricochet/tui/messages.hh"

#include <chrono>
#include <cstdio>
#include <memory>
#include <optional>
#include <string>
#include <vector>

namespace ricochet::tui {

namespace detail {
class Mailbox;
}

struct TerminalOptions
{
  bool capture_mouse { true };
  // Explicit host opt-in: terminfo does not establish bracketed-paste support.
  bool bracketed_paste { false };
  std::chrono::milliseconds escape_delay { 25 };
};

struct TerminalCapabilities
{
  bool colors { false };
  bool mouse_buttons { false };
  bool mouse_motion { false };
  bool bracketed_paste { false };
};

enum class PostResult : std::uint8_t
{
  Queued,
  Closed
};

// A sender can outlive the UI session. Posting is thread-safe; it never calls curses.
class UpdateSender
{
public:
  PostResult post_update( HostUpdate update ) const;

private:
  friend class Session;
  explicit UpdateSender( std::shared_ptr<detail::Mailbox> mailbox );
  std::shared_ptr<detail::Mailbox> mailbox_;
};

struct StepResult
{
  bool running { true };
  // Incremental document updates and editing are subsequent milestones. Until then,
  // unsupported updates are returned intact rather than silently acknowledged.
  std::vector<HostUpdate> unhandled_updates {};
  std::optional<TerminalClosed> terminal_closed {};
  std::optional<std::string> error {};
};

class Session
{
public:
  explicit Session( TerminalOptions options = {} );
  // Streams must remain open until destruction. Construction, step, close and
  // destruction run on the same UI thread; only one terminal session may exist.
  Session( std::FILE& input, std::FILE& output, TerminalOptions options = {} );
  ~Session();

  Session( const Session& ) = delete;
  Session& operator=( const Session& ) = delete;
  Session( Session&& ) = delete;
  Session& operator=( Session&& ) = delete;

  UpdateSender sender() const;
  PostResult post_update( HostUpdate update );
  // max_wait bounds idle waiting. Decoding a partial terminal key sequence can
  // additionally wait for escape_delay; processing batches is bounded separately.
  StepResult step( std::chrono::milliseconds max_wait );
  std::vector<UserAction> take_actions();
  std::optional<ViewSnapshot> snapshot( TabId tab ) const;
  TerminalCapabilities capabilities() const;
  void close() noexcept;

private:
  class Impl;
  std::unique_ptr<Impl> impl_;
};

} // namespace ricochet::tui