Skip to main content

zeph_tui/
types.rs

1// SPDX-FileCopyrightText: 2026 Andrei G <bug-ops>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4/// Metadata for an active paste in the input buffer.
5///
6/// Present only while the input contains unsubmitted pasted text that was
7/// multiline (two or more lines). Single-line pastes do not set this.
8///
9/// # Examples
10///
11/// ```rust
12/// use zeph_tui::PasteState;
13///
14/// let ps = PasteState { line_count: 5, byte_len: 128 };
15/// assert_eq!(ps.line_count, 5);
16/// ```
17#[derive(Debug, Clone)]
18pub struct PasteState {
19    /// Number of lines in the pasted text (always >= 2).
20    pub line_count: usize,
21    /// Byte length of the pasted text.
22    pub byte_len: usize,
23}
24
25/// The current text-input mode of the TUI.
26///
27/// Inspired by modal editors: in `Normal` mode key bindings trigger actions;
28/// in `Insert` mode printable characters are appended to the input buffer.
29///
30/// # Examples
31///
32/// ```rust
33/// use zeph_tui::InputMode;
34///
35/// let mode = InputMode::Insert;
36/// assert_eq!(mode, InputMode::Insert);
37/// ```
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39pub enum InputMode {
40    /// Navigation and command keybindings are active; typing does not insert text.
41    Normal,
42    /// Text is inserted into the input field on every printable key press.
43    Insert,
44}
45
46/// The role of a message displayed in the chat widget.
47///
48/// The role controls the display style (colour, prefix label) applied by the
49/// chat renderer.
50///
51/// # Examples
52///
53/// ```rust
54/// use zeph_tui::MessageRole;
55///
56/// let role = MessageRole::User;
57/// assert_eq!(role, MessageRole::User);
58/// ```
59#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60pub enum MessageRole {
61    /// A message sent by the human user.
62    User,
63    /// A message generated by the AI assistant.
64    Assistant,
65    /// An internal system or meta message (e.g. session start notice).
66    System,
67    /// Output from a tool call execution.
68    Tool,
69}
70
71/// A single entry in the TUI chat history buffer.
72///
73/// Carries the rendered text, role metadata, optional tool context, an inline
74/// diff, and a wall-clock timestamp for display.
75///
76/// # Examples
77///
78/// ```rust
79/// use zeph_tui::{ChatMessage, MessageRole};
80///
81/// let msg = ChatMessage::new(MessageRole::User, "Hello, agent!");
82/// assert_eq!(msg.role, MessageRole::User);
83/// assert_eq!(msg.content, "Hello, agent!");
84/// assert!(!msg.streaming);
85/// ```
86#[derive(Debug, Clone)]
87pub struct ChatMessage {
88    /// Role that determines rendering style.
89    pub role: MessageRole,
90    /// Rendered text content of the message.
91    pub content: String,
92    /// `true` while the message is still being streamed from the LLM.
93    pub streaming: bool,
94    /// Name of the tool that produced this message, if any.
95    pub tool_name: Option<zeph_common::ToolName>,
96    /// Inline diff attached to a tool-output message.
97    pub diff_data: Option<zeph_core::DiffData>,
98    /// Human-readable filter statistics (e.g. "kept 12/40 lines").
99    pub filter_stats: Option<String>,
100    /// 0-based line indices preserved by the output filter, used for
101    /// highlighting in the diff widget.
102    pub kept_lines: Option<Vec<usize>>,
103    /// Wall-clock time formatted as `HH:MM` when the message was created.
104    pub timestamp: String,
105    /// Number of lines in the pasted content when this message was submitted
106    /// from a paste. `Some(n)` (n >= 2) enables collapsible display in the
107    /// chat renderer; `None` means normal display.
108    pub paste_line_count: Option<usize>,
109}
110
111impl ChatMessage {
112    /// Create a new non-streaming message with the current local time as timestamp.
113    ///
114    /// # Examples
115    ///
116    /// ```rust
117    /// use zeph_tui::{ChatMessage, MessageRole};
118    ///
119    /// let msg = ChatMessage::new(MessageRole::Assistant, "Done.");
120    /// assert_eq!(msg.role, MessageRole::Assistant);
121    /// assert!(!msg.streaming);
122    /// ```
123    pub fn new(role: MessageRole, content: impl Into<String>) -> Self {
124        Self {
125            role,
126            content: content.into(),
127            streaming: false,
128            tool_name: None,
129            diff_data: None,
130            filter_stats: None,
131            kept_lines: None,
132            timestamp: format_local_time(),
133            paste_line_count: None,
134        }
135    }
136
137    /// Mark this message as actively streaming.
138    ///
139    /// The chat widget renders a blinking cursor after the content while
140    /// `streaming` is `true`.
141    ///
142    /// # Examples
143    ///
144    /// ```rust
145    /// use zeph_tui::{ChatMessage, MessageRole};
146    ///
147    /// let msg = ChatMessage::new(MessageRole::Assistant, "").streaming();
148    /// assert!(msg.streaming);
149    /// ```
150    #[must_use]
151    pub fn streaming(mut self) -> Self {
152        self.streaming = true;
153        self
154    }
155
156    /// Attach a tool name to this message for display in the chat header.
157    ///
158    /// # Examples
159    ///
160    /// ```rust
161    /// use zeph_tui::{ChatMessage, MessageRole};
162    ///
163    /// let msg = ChatMessage::new(MessageRole::Tool, "output")
164    ///     .with_tool(zeph_common::ToolName::new("bash"));
165    /// assert!(msg.tool_name.is_some());
166    /// ```
167    #[must_use]
168    pub fn with_tool(mut self, name: zeph_common::ToolName) -> Self {
169        self.tool_name = Some(name);
170        self
171    }
172}
173
174fn format_local_time() -> String {
175    chrono::Local::now().format("%H:%M").to_string()
176}