nu_explore/explore_regex/
command.rs1use crate::explore_regex::app::App;
29use crate::explore_regex::ui::run_app_loop;
30use nu_engine::command_prelude::*;
31use ratatui::{
32 Terminal,
33 backend::CrosstermBackend,
34 crossterm::{
35 event::{DisableMouseCapture, EnableMouseCapture},
36 execute,
37 terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode},
38 },
39};
40use std::io;
41
42#[derive(Clone)]
44pub struct ExploreRegex;
45
46impl Command for ExploreRegex {
47 fn name(&self) -> &str {
48 "explore regex"
49 }
50
51 fn description(&self) -> &str {
52 "Launch a TUI to create and explore regular expressions interactively."
53 }
54
55 fn signature(&self) -> nu_protocol::Signature {
56 Signature::build("explore regex")
57 .input_output_types(vec![
58 (Type::Nothing, Type::String),
59 (Type::String, Type::String),
60 ])
61 .category(Category::Viewers)
62 }
63
64 fn extra_description(&self) -> &str {
65 r#"Press `Ctrl-Q` to quit and provide constructed regular expression as the output."#
66 }
67
68 fn run(
69 &self,
70 _engine_state: &EngineState,
71 _stack: &mut Stack,
72 call: &Call,
73 input: PipelineData,
74 ) -> Result<PipelineData, ShellError> {
75 let input_span = input.span().unwrap_or(call.head);
76 let (string_input, _span, _metadata) = input.collect_string_strict(input_span)?;
77 let regex = execute_regex_app(call.head, string_input)?;
78
79 Ok(PipelineData::Value(
80 nu_protocol::Value::string(regex, call.head),
81 None,
82 ))
83 }
84
85 fn examples(&self) -> Vec<Example<'_>> {
86 vec![
87 Example {
88 description: "Explore a regular expression interactively",
89 example: r#"explore regex"#,
90 result: None,
91 },
92 Example {
93 description: "Explore a regular expression interactively with sample text",
94 example: r#"open -r Cargo.toml | explore regex"#,
95 result: None,
96 },
97 ]
98 }
99}
100
101fn terminal_error(error: &str, cause: impl std::fmt::Display, span: Span) -> ShellError {
103 ShellError::GenericError {
104 error: error.into(),
105 msg: format!("terminal error: {cause}"),
106 span: Some(span),
107 help: None,
108 inner: vec![],
109 }
110}
111
112fn execute_regex_app(span: Span, string_input: String) -> Result<String, ShellError> {
113 let mut terminal = setup_terminal(span)?;
114 let mut app = App::new(string_input);
115
116 let result = run_app_loop(&mut terminal, &mut app);
117
118 let restore_result = restore_terminal(&mut terminal, span);
120
121 result.map_err(|e| terminal_error("Application error", e, span))?;
123 restore_result?;
124
125 Ok(app.get_regex_input())
126}
127
128fn setup_terminal(span: Span) -> Result<Terminal<CrosstermBackend<io::Stdout>>, ShellError> {
129 enable_raw_mode().map_err(|e| terminal_error("Could not enable raw mode", e, span))?;
130
131 let mut stdout = io::stdout();
132 execute!(stdout, EnterAlternateScreen, EnableMouseCapture)
133 .map_err(|e| terminal_error("Could not enter alternate screen", e, span))?;
134
135 Terminal::new(CrosstermBackend::new(stdout))
136 .map_err(|e| terminal_error("Could not initialize terminal", e, span))
137}
138
139fn restore_terminal(
140 terminal: &mut Terminal<CrosstermBackend<io::Stdout>>,
141 span: Span,
142) -> Result<(), ShellError> {
143 disable_raw_mode().map_err(|e| terminal_error("Could not disable raw mode", e, span))?;
144
145 execute!(
146 terminal.backend_mut(),
147 LeaveAlternateScreen,
148 DisableMouseCapture
149 )
150 .map_err(|e| terminal_error("Could not leave alternate screen", e, span))?;
151
152 terminal
153 .show_cursor()
154 .map_err(|e| terminal_error("Could not show terminal cursor", e, span))
155}