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