lazytables/commands/
basic.rs1use super::{Command, CommandAction, CommandCategory, CommandContext, CommandId, CommandResult};
4use crate::core::error::Result;
5
6pub struct QuitCommand;
8
9impl Command for QuitCommand {
10 fn execute(&self, _context: &mut CommandContext) -> Result<CommandResult> {
11 Ok(CommandResult::Action(CommandAction::Quit))
12 }
13
14 fn description(&self) -> &str {
15 "Quit the application"
16 }
17
18 fn id(&self) -> CommandId {
19 CommandId::Quit
20 }
21
22 fn shortcut(&self) -> Option<String> {
23 Some("q".to_string())
24 }
25
26 fn category(&self) -> CommandCategory {
27 CommandCategory::General
28 }
29}
30
31pub struct ForceQuitCommand;
33
34impl Command for ForceQuitCommand {
35 fn execute(&self, _context: &mut CommandContext) -> Result<CommandResult> {
36 Ok(CommandResult::Action(CommandAction::Quit))
37 }
38
39 fn description(&self) -> &str {
40 "Force quit the application without confirmation"
41 }
42
43 fn id(&self) -> CommandId {
44 CommandId::ForceQuit
45 }
46
47 fn shortcut(&self) -> Option<String> {
48 Some("Q".to_string())
49 }
50
51 fn category(&self) -> CommandCategory {
52 CommandCategory::General
53 }
54}
55
56pub struct HelpCommand;
58
59impl Command for HelpCommand {
60 fn execute(&self, context: &mut CommandContext) -> Result<CommandResult> {
61 use crate::app::state::HelpMode;
62 use crate::state::view::OverlayView;
63
64 context.state.ui.help_mode = match context.state.ui.focused_pane {
66 crate::app::FocusedPane::Connections => HelpMode::Connections,
67 crate::app::FocusedPane::Tables => HelpMode::Tables,
68 crate::app::FocusedPane::Details => HelpMode::Details,
69 crate::app::FocusedPane::TabularOutput => HelpMode::TabularOutput,
70 crate::app::FocusedPane::QueryWindow => HelpMode::QueryWindow,
71 crate::app::FocusedPane::SqlFiles => HelpMode::SqlFiles,
72 };
73
74 context.state.ui.show_overlay(OverlayView::Help);
76
77 context.state.ui.reset_help_modal_state();
79
80 Ok(CommandResult::SuccessWithMessage(
81 "Help overlay opened".to_string(),
82 ))
83 }
84
85 fn description(&self) -> &str {
86 "Show context-aware help"
87 }
88
89 fn id(&self) -> CommandId {
90 CommandId::Help
91 }
92
93 fn shortcut(&self) -> Option<String> {
94 Some("?".to_string())
95 }
96
97 fn category(&self) -> CommandCategory {
98 CommandCategory::General
99 }
100}
101
102pub struct ToggleHelpCommand;
104
105impl Command for ToggleHelpCommand {
106 fn execute(&self, context: &mut CommandContext) -> Result<CommandResult> {
107 use crate::app::state::HelpMode;
108
109 if context.state.ui.help_mode != HelpMode::None {
110 context.state.ui.help_mode = HelpMode::None;
112 context.state.ui.return_to_main();
113 Ok(CommandResult::SuccessWithMessage(
114 "Help overlay closed".to_string(),
115 ))
116 } else {
117 HelpCommand.execute(context)
119 }
120 }
121
122 fn description(&self) -> &str {
123 "Toggle help overlay"
124 }
125
126 fn id(&self) -> CommandId {
127 CommandId::ToggleHelp
128 }
129
130 fn category(&self) -> CommandCategory {
131 CommandCategory::General
132 }
133}
134
135pub struct SaveCommand;
137
138impl Command for SaveCommand {
139 fn execute(&self, context: &mut CommandContext) -> Result<CommandResult> {
140 match context.state.ui.focused_pane {
142 crate::app::FocusedPane::QueryWindow => {
143 if !context.state.query_content.is_empty() {
145 let query = &context.state.query_content;
146 let timestamp = chrono::Local::now().format("%Y%m%d_%H%M%S");
148 let filename = format!("query_{timestamp}.sql");
149
150 let sql_dir = dirs::home_dir()
152 .unwrap_or_else(|| std::path::PathBuf::from("."))
153 .join(".lazytables")
154 .join("sql_files");
155
156 let filepath = sql_dir.join(&filename);
157
158 tokio::runtime::Handle::current().block_on(async {
161 crate::io::async_fs::create_dir_all(&sql_dir).await?;
163 crate::io::async_fs::write(&filepath, query).await
165 })?;
166
167 context
169 .state
170 .toast_manager
171 .success(format!("Query saved to {filename}"));
172
173 Ok(CommandResult::SuccessWithMessage(format!(
174 "Query saved to {}",
175 filepath.display()
176 )))
177 } else {
178 Ok(CommandResult::Error("No query to save".to_string()))
179 }
180 }
181 crate::app::FocusedPane::TabularOutput => {
182 Ok(CommandResult::Error(
185 "Export not yet implemented".to_string(),
186 ))
187 }
188 _ => Ok(CommandResult::Error(
189 "Nothing to save in current pane".to_string(),
190 )),
191 }
192 }
193
194 fn description(&self) -> &str {
195 "Save current content"
196 }
197
198 fn id(&self) -> CommandId {
199 CommandId::Save
200 }
201
202 fn shortcut(&self) -> Option<String> {
203 Some("Ctrl+s".to_string())
204 }
205
206 fn category(&self) -> CommandCategory {
207 CommandCategory::File
208 }
209
210 fn can_execute(&self, context: &CommandContext) -> bool {
211 match context.state.ui.focused_pane {
213 crate::app::FocusedPane::QueryWindow => !context.state.query_content.is_empty(),
214 crate::app::FocusedPane::TabularOutput => {
215 false
217 }
218 _ => false,
219 }
220 }
221}
222
223pub struct SaveAsCommand;
225
226impl Command for SaveAsCommand {
227 fn execute(&self, context: &mut CommandContext) -> Result<CommandResult> {
228 SaveCommand.execute(context)
231 }
232
233 fn description(&self) -> &str {
234 "Save current content with a specific name"
235 }
236
237 fn id(&self) -> CommandId {
238 CommandId::SaveAs
239 }
240
241 fn shortcut(&self) -> Option<String> {
242 Some("Ctrl+Shift+s".to_string())
243 }
244
245 fn category(&self) -> CommandCategory {
246 CommandCategory::File
247 }
248}
249
250pub struct OpenCommand;
252
253impl Command for OpenCommand {
254 fn execute(&self, context: &mut CommandContext) -> Result<CommandResult> {
255 match context.state.ui.focused_pane {
256 crate::app::FocusedPane::SqlFiles => {
257 {
259 let selected = context.state.ui.selected_sql_file;
260 let sql_dir = dirs::home_dir()
261 .unwrap_or_else(|| std::path::PathBuf::from("."))
262 .join(".lazytables")
263 .join("sql_files");
264
265 let files = tokio::runtime::Handle::current().block_on(async {
268 let entries = crate::io::async_fs::read_dir(&sql_dir).await?;
269 let filtered: Vec<_> = entries
270 .into_iter()
271 .filter(|entry| {
272 entry
273 .path()
274 .extension()
275 .and_then(|ext| ext.to_str())
276 .map(|ext| ext == "sql")
277 .unwrap_or(false)
278 })
279 .collect();
280 Ok::<_, crate::core::error::LazyTablesError>(filtered)
281 })?;
282
283 if selected < files.len() {
284 let content = tokio::runtime::Handle::current().block_on(async {
285 crate::io::async_fs::read_to_string(files[selected].path()).await
286 })?;
287 context.state.query_content = content;
288 context.state.ui.current_sql_file =
289 Some(files[selected].file_name().to_string_lossy().to_string());
290
291 Ok(CommandResult::SuccessWithMessage(format!(
292 "Loaded {}",
293 files[selected].file_name().to_string_lossy()
294 )))
295 } else {
296 Ok(CommandResult::Error("Invalid file selection".to_string()))
297 }
298 }
299 }
300 _ => Ok(CommandResult::Error(
301 "Navigate to SQL Files pane to open a file".to_string(),
302 )),
303 }
304 }
305
306 fn description(&self) -> &str {
307 "Open a file"
308 }
309
310 fn id(&self) -> CommandId {
311 CommandId::Open
312 }
313
314 fn shortcut(&self) -> Option<String> {
315 Some("Ctrl+o".to_string())
316 }
317
318 fn category(&self) -> CommandCategory {
319 CommandCategory::File
320 }
321}