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