• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

dustinblackman / oatmeal / 7041200011

30 Nov 2023 02:24AM UTC coverage: 49.72% (-0.07%) from 49.79%
7041200011

push

github

dustinblackman
fix: Exit warning on Windows

0 of 4 new or added lines in 1 file covered. (0.0%)

1 existing line in 1 file now uncovered.

1066 of 2144 relevant lines covered (49.72%)

16.26 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/application/ui.rs
1
use std::io;
2

3
use anyhow::Result;
4
use crossterm::cursor;
5
use crossterm::event::DisableMouseCapture;
6
use crossterm::event::EnableMouseCapture;
7
use crossterm::terminal::disable_raw_mode;
8
use crossterm::terminal::enable_raw_mode;
9
use crossterm::terminal::is_raw_mode_enabled;
10
use crossterm::terminal::EnterAlternateScreen;
11
use crossterm::terminal::LeaveAlternateScreen;
12
use ratatui::backend::CrosstermBackend;
13
use ratatui::prelude::*;
14
use ratatui::widgets::Scrollbar;
15
use ratatui::widgets::ScrollbarOrientation;
16
use ratatui::Terminal;
17
use tokio::sync::mpsc;
18

19
use crate::config::Config;
20
use crate::config::ConfigKey;
21
use crate::domain::models::Action;
22
use crate::domain::models::Author;
23
use crate::domain::models::BackendPrompt;
24
use crate::domain::models::Event;
25
use crate::domain::models::Loading;
26
use crate::domain::models::Message;
27
use crate::domain::models::SlashCommand;
28
use crate::domain::models::TextArea;
29
use crate::domain::services::events::EventsService;
30
use crate::domain::services::AppState;
31
use crate::infrastructure::editors::EditorManager;
32

33
async fn start_loop<B: Backend>(
×
34
    terminal: &mut Terminal<B>,
×
35
    app_state: &mut AppState<'_>,
×
36
    tx: mpsc::UnboundedSender<Action>,
×
37
    rx: mpsc::UnboundedReceiver<Event>,
×
38
) -> Result<()> {
×
39
    let mut events = EventsService::new(rx);
×
40
    let mut textarea = TextArea::default();
×
41
    let loading = Loading::default();
×
42

43
    #[cfg(feature = "dev")]
44
    {
45
        use tui_textarea::Input;
46
        use tui_textarea::Key;
47

48
        let test_str = "Write a function in Java that prints from 0 to 10. Return in markdown, add language to code blocks, describe the example before and after.";
49
        for char in test_str.chars() {
50
            textarea.input(Input {
51
                key: Key::Char(char),
52
                ctrl: false,
53
                alt: false,
54
                shift: false,
55
            });
56
        }
57
    }
58

59
    loop {
60
        terminal.draw(|frame| {
×
61
            let layout = Layout::default()
×
62
                .direction(Direction::Vertical)
×
63
                .constraints(vec![Constraint::Min(1), Constraint::Max(4)])
×
64
                .split(frame.size());
×
65

×
66
            if layout[0].width as usize != app_state.last_known_width
×
67
                || layout[0].height as usize != app_state.last_known_height
×
68
            {
×
69
                app_state.set_rect(layout[0]);
×
70
            }
×
71

72
            app_state
×
73
                .bubble_list
×
74
                .render(frame, layout[0], app_state.scroll.position);
×
75
            frame.render_stateful_widget(
×
76
                Scrollbar::new(ScrollbarOrientation::VerticalRight),
×
77
                layout[0].inner(&Margin {
×
78
                    vertical: 1,
×
79
                    horizontal: 0,
×
80
                }),
×
81
                &mut app_state.scroll.scrollbar_state,
×
82
            );
×
83

×
84
            if app_state.waiting_for_backend {
×
85
                loading.render(frame, layout[1]);
×
86
            } else {
×
87
                frame.render_widget(textarea.widget(), layout[1]);
×
88
            }
×
89
        })?;
×
90

91
        macro_rules! send_user_message {
92
            ( $input_str:expr ) => {
93
                let input_str = $input_str;
94

95
                let msg = Message::new(Author::User, &input_str);
96
                textarea = TextArea::default();
97
                app_state.add_message(msg);
98

99
                let (should_break, should_continue) =
100
                    app_state.handle_slash_commands(input_str, &tx)?;
101
                if should_break {
102
                    break;
103
                }
104
                if should_continue {
105
                    continue;
106
                }
107

108
                app_state.waiting_for_backend = true;
109
                let mut prompt =
110
                    BackendPrompt::new(input_str.to_string(), app_state.backend_context.clone());
111

112
                if app_state.backend_context.is_empty() && SlashCommand::parse(&input_str).is_none()
113
                {
114
                    prompt.append_system_prompt(&app_state.editor_context);
115
                }
116

117
                tx.send(Action::BackendRequest(prompt))?;
118
                app_state.save_session().await?;
119
            };
120
        }
121

122
        match events.next().await? {
×
123
            Event::BackendMessage(msg) => {
×
124
                app_state.add_message(msg);
×
125
                app_state.waiting_for_backend = false;
×
126
            }
×
127
            Event::BackendPromptResponse(msg) => {
×
128
                app_state.handle_backend_response(msg.clone());
×
129
                if msg.done {
×
130
                    app_state.save_session().await?;
×
131
                }
×
132
            }
133
            Event::KeyboardCharInput(input) => {
×
134
                if app_state.waiting_for_backend {
×
135
                    continue;
×
136
                }
×
137

×
NEW
138
                // Windows submits a null event right after CTRL+C. Ignore it.
×
NEW
139
                if input.key != tui_textarea::Key::Null {
×
NEW
140
                    app_state.exit_warning = false;
×
NEW
141
                }
×
142

UNCOV
143
                textarea.input(input);
×
144
            }
145
            Event::KeyboardCTRLC() => {
146
                if app_state.waiting_for_backend {
×
147
                    app_state.waiting_for_backend = false;
×
148
                    tx.send(Action::BackendAbort())?;
×
149
                } else if !app_state.exit_warning {
×
150
                    app_state.add_message(Message::new(
×
151
                        Author::Oatmeal,
×
152
                        "If you wish to quit, hit CTRL+C one more time, or use /quit",
×
153
                    ));
×
154
                    app_state.exit_warning = true;
×
155
                } else {
×
156
                    break;
×
157
                }
158
            }
159
            Event::KeyboardCTRLR() => {
160
                let last_message = app_state
×
161
                    .messages
×
162
                    .iter()
×
163
                    .filter(|msg| {
×
164
                        return msg.author == Author::User
×
165
                            && SlashCommand::parse(&msg.text).is_none();
×
166
                    })
×
167
                    .last();
×
168
                if let Some(message) = last_message.cloned() {
×
169
                    send_user_message!(&message.text);
×
170
                }
×
171
            }
172
            Event::KeyboardEnter() => {
173
                if app_state.waiting_for_backend {
×
174
                    continue;
×
175
                }
×
176
                let input_str = &textarea.lines().join("\n");
×
177
                if input_str.is_empty() {
×
178
                    continue;
×
179
                }
×
180
                send_user_message!(input_str);
×
181
            }
182
            Event::UIResize() => {
183
                continue;
×
184
            }
185
            Event::UIScrollDown() => {
×
186
                app_state.scroll.down();
×
187
            }
×
188
            Event::UIScrollUp() => {
×
189
                app_state.scroll.up();
×
190
            }
×
191
            Event::UIScrollPageDown() => {
×
192
                app_state.scroll.down_page();
×
193
            }
×
194
            Event::UIScrollPageUp() => {
×
195
                app_state.scroll.up_page();
×
196
            }
×
197
        }
198
    }
199

200
    return Ok(());
×
201
}
×
202

203
pub fn destruct_terminal_for_panic() {
×
204
    if is_raw_mode_enabled().unwrap() {
×
205
        disable_raw_mode().unwrap();
×
206
        crossterm::execute!(io::stdout(), LeaveAlternateScreen, DisableMouseCapture).unwrap();
×
207
        crossterm::execute!(io::stdout(), cursor::Show).unwrap();
×
208
    }
×
209
}
×
210

211
pub async fn start(
×
212
    tx: mpsc::UnboundedSender<Action>,
×
213
    rx: mpsc::UnboundedReceiver<Event>,
×
214
) -> Result<()> {
×
215
    let stdout = io::stdout();
×
216
    let mut stdout = stdout.lock();
×
217

×
218
    enable_raw_mode()?;
×
219
    crossterm::execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
×
220
    let term_backend = CrosstermBackend::new(stdout);
×
221
    let mut terminal = Terminal::new(term_backend)?;
×
222
    let editor_name = Config::get(ConfigKey::Editor);
×
223
    let mut app_state = AppState::new(
×
224
        &Config::get(ConfigKey::Backend),
×
225
        &editor_name,
×
226
        &Config::get(ConfigKey::Model),
×
227
        &Config::get(ConfigKey::Theme),
×
228
        &Config::get(ConfigKey::ThemeFile),
×
229
        &Config::get(ConfigKey::SessionID),
×
230
    )
×
231
    .await?;
×
232

233
    start_loop(&mut terminal, &mut app_state, tx, rx).await?;
×
234
    if !editor_name.is_empty() {
×
235
        let editor = EditorManager::get(&editor_name)?;
×
236
        if editor.health_check().await.is_ok() {
×
237
            editor.clear_context().await?;
×
238
        }
×
239
    }
×
240

241
    disable_raw_mode()?;
×
242
    crossterm::execute!(
×
243
        terminal.backend_mut(),
×
244
        LeaveAlternateScreen,
×
245
        DisableMouseCapture
×
246
    )?;
×
247
    terminal.show_cursor()?;
×
248

249
    return Ok(());
×
250
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc