1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
use super::*;
impl Repl {
/// Run without R (standalone mode).
pub(super) fn run_standalone(&self) -> Result<()> {
// Create line editor with bracketed paste enabled
let line_editor = self.configure_auto_pairs(Reedline::create().use_bracketed_paste(true));
// Set up SQLite-backed history for R mode
let history_handle = self.prepared_r_history();
let mut line_editor = history_handle.attach_to_editor(line_editor);
// Meta commands use the already-prepared shell owner directly.
let shell_history_handle = self.prepared_shell_history();
// Only an available R runtime is advertised for IPC history filtering.
if !history_handle.is_available() {
crate::ipc::clear_history_session_id();
}
let history_session_id = if history_handle.is_available() {
self.history_session_id_raw()
} else {
None
};
// Set up edit mode with conditional ':' keybinding
let editor_state = new_editor_state_ref();
line_editor = match self.config.editor.mode {
EditorMode::Vi => {
let mut insert_keybindings = default_vi_insert_keybindings();
add_common_keybindings(&mut insert_keybindings);
if self.config.experimental.shell_semicolon_shortcut {
add_shell_semicolon_keybinding(&mut insert_keybindings);
}
add_key_map_keybindings(&mut insert_keybindings, &self.config.editor.key_map);
let vi = Vi::new(
insert_keybindings,
default_vi_normal_keybindings(),
default_vi_visual_keybindings(),
);
line_editor.with_edit_mode(wrap_edit_mode_with_conditional_rules(
vi,
editor_state.clone(),
self.config.experimental.completion_min_chars,
self.config.experimental.shell_semicolon_shortcut,
))
}
EditorMode::Emacs => {
let mut keybindings = default_emacs_keybindings();
add_common_keybindings(&mut keybindings);
if self.config.experimental.shell_semicolon_shortcut {
add_shell_semicolon_keybinding(&mut keybindings);
}
add_key_map_keybindings(&mut keybindings, &self.config.editor.key_map);
let emacs = Emacs::new(keybindings);
line_editor.with_edit_mode(wrap_edit_mode_with_conditional_rules(
emacs,
editor_state.clone(),
self.config.experimental.completion_min_chars,
self.config.experimental.shell_semicolon_shortcut,
))
}
};
let highlighter = CombinedHighlighter::new(
self.config.colors.clone(),
self.config.editor.highlight_matching_bracket,
)
.with_editor_state(editor_state.clone());
line_editor = line_editor.with_highlighter(Box::new(highlighter));
// Set up history-based autosuggestion (fish/nushell style)
// Uses RLanguageHinter for proper R token handling (e.g., |> as single token)
if let Some(hinter) = self.create_r_hinter() {
line_editor = line_editor.with_hinter(hinter);
}
// Mode indicator for special modes (reprex, etc.)
let mode_position = self.config.prompt.mode_indicator;
let mode_indicator = match self.config.startup.reprex {
ReprexMode::Off => None,
ReprexMode::On if mode_position != ModeIndicatorPosition::None => {
Some(self.config.prompt.indicators.reprex.clone())
}
ReprexMode::Format if mode_position != ModeIndicatorPosition::None => {
Some(self.config.prompt.indicators.reprex_format.clone())
}
_ => None,
};
let prompt = RPrompt::new(
self.prompt_formatter.format(&self.config.prompt.format),
self.prompt_formatter
.format(&self.config.prompt.continuation),
)
.with_mode_indicator(mode_indicator, mode_position)
.with_colors(
self.config.colors.prompt.main,
self.config.colors.prompt.continuation,
self.config.colors.prompt.indicator,
);
// Minimal prompt config for meta commands (R not available)
let mut prompt_config =
PromptRuntimeConfig::builder(self.prompt_formatter.clone(), "R > ", "+ ", "$ ")
.mode_indicator_position(ModeIndicatorPosition::None)
.main_color(self.config.colors.prompt.main)
.continuation_color(self.config.colors.prompt.continuation)
.shell_color(self.config.colors.prompt.shell)
.mode_indicator_color(self.config.colors.prompt.indicator)
.status(
self.config.prompt.status.clone(),
self.config.colors.prompt.status.clone(),
)
.duration(
self.config.experimental.prompt_duration.clone(),
self.config.colors.prompt.duration,
)
.spinner(self.config.experimental.prompt_spinner.clone())
.vi(
self.config.prompt.vi.clone(),
self.config.colors.prompt.vi.clone(),
)
.build();
let mut standalone_reprex = ReprexRuntime::from_resolved(
self.config.startup.reprex,
self.config.reprex.comment.clone(),
self.config.reprex.formatter,
self.formatter_backend,
);
// Separate dir_stack for standalone mode (R not initialized).
// The R mainloop path stores its own dir_stack in ReplState.
// These two paths are mutually exclusive, so no sharing is needed.
let mut dir_stack: Vec<std::path::PathBuf> = Vec::new();
loop {
let read_result = line_editor.read_line(&prompt);
// Keep startup echo suppression through the raw-mode transition.
// Restore the original cooked mode after reedline returns so its
// raw-mode handoff has no interval in which input can be echoed.
if read_result.is_ok()
&& let Err(error) = crate::console_mode::handoff_to_reedline()
{
eprintln!("Error: {}", error);
break;
}
match read_result {
Ok(Signal::Success(line)) => {
let save_outcome = history_handle.receipt_outcome();
let trimmed = line.trim();
if trimmed.is_empty() {
// A whitespace-only buffer is still saved by reedline,
// so record that it is an ordinary line before skipping.
finalize_history(Some(&history_handle), save_outcome, false);
continue;
}
// Process meta commands even when R is not initialized
// This allows :switch, :quit, :shell, etc. to work
if let Some(result) = process_meta_command(
&line,
&mut prompt_config,
&mut standalone_reprex,
&history_handle,
&shell_history_handle,
&self.r_source_status,
&mut dir_stack,
history_session_id,
self.r_home.as_deref(),
) {
finalize_history(Some(&history_handle), save_outcome, true);
// Clear duration so the previous R command's time
// does not persist in the prompt after a meta command.
prompt_config.clear_command_duration();
let ctx = SessionInfoContext {
prompt_config: &prompt_config,
reprex: &standalone_reprex,
config_file_info: &self.config_file_info,
history_location: &self.history_location,
r_history: &history_handle,
shell_history: &shell_history_handle,
r_source_status: &self.r_source_status,
};
match handle_meta_command_result(result, &ctx) {
MetaAction::Continue => continue,
MetaAction::Exit => {
println!("\nGoodbye!");
return Ok(());
}
}
}
finalize_history(Some(&history_handle), save_outcome, false);
// Not a meta command - show R not initialized message
println!("{}", format!("[R not initialized] {}", line).dark_grey());
}
Ok(Signal::CtrlC) => {
// Clear any visible completion menu before printing ^C
let _ = io::stdout().execute(terminal::Clear(ClearType::FromCursorDown));
println!("^C");
continue;
}
Ok(Signal::CtrlD) => {
// Clear any visible menu before printing farewell message
let _ = io::stdout().execute(terminal::Clear(ClearType::FromCursorDown));
println!("\nGoodbye!");
break;
}
Ok(_) => {
// ExternalBreak or future variants: ignore in standalone mode
continue;
}
Err(err) => {
eprintln!("Error: {}", err);
break;
}
}
}
Ok(())
}
}