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
use super::*;
impl Repl {
/// Run with R's main loop (run_Rmainloop).
pub(super) fn run_with_r_mainloop(&self) -> Result<()> {
let (line_editor, r_history_handle) = self.create_r_line_editor();
// Create shell line editor with separate history
let (shell_line_editor, shell_history_handle) = self.create_shell_line_editor();
// The R runtime was registered before the IPC server started; shell
// history remains a separate owner for shell-mode commands.
// Create prompt runtime config with unexpanded templates
// Templates are expanded dynamically in build_main_prompt() to track cwd changes
let prompt_config = PromptRuntimeConfig::builder(
self.prompt_formatter.clone(),
self.config.prompt.format.clone(),
self.config.prompt.continuation.clone(),
self.config.prompt.shell_format.clone(),
)
.mode_indicator_position(self.config.prompt.mode_indicator)
.indicators(self.config.prompt.indicators.clone())
.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();
// Get history paths for :history commands
// Store state in thread-local
REPL_STATE.with(|state| {
*state.borrow_mut() = Some(ReplState {
line_editor,
shell_line_editor,
prompt_config,
reprex: ReprexRuntime::from_resolved(
self.config.startup.reprex,
self.config.reprex.comment.clone(),
self.config.reprex.formatter,
self.formatter_backend,
),
should_exit: false,
r_prompt_options_ambiguous: false,
config_path: self.config_path.clone(),
config_status: self.config_status,
r_source_status: self.r_source_status.clone(),
r_home: self.r_home.clone(),
history_location: self.history_location.clone(),
forget_config: self.config.experimental.history_forget.clone(),
sponge_queue: state::SpongeQueue::new(),
dir_stack: Vec::new(),
// IPC advertises the R runtime's session only; shell history
// remains separately owned and is not an IPC filter source.
history_session_id: if r_history_handle.is_available() {
self.session_id
} else {
None
},
r_history: r_history_handle,
shell_history: shell_history_handle,
pending_history_context: PendingHistoryContext::None,
});
});
// Initialize global error handler for rlang/dplyr error detection
// This sets up globalCallingHandlers() to track error conditions
// that output to stdout instead of stderr
let error_handler_code = arf_libr::global_error_handler_code();
match arf_harp::eval_string_with_visibility(error_handler_code) {
Ok(_) => {
log::info!("Global error handler initialized");
arf_libr::mark_global_error_handler_initialized();
}
Err(e) => {
log::warn!("Failed to initialize global error handler: {:?}", e);
}
}
// Initialize askpass handler (Unix only) to bypass reedline for password input.
#[cfg(unix)]
{
let askpass_handler_code = arf_libr::askpass_handler_code();
match arf_harp::eval_string_with_visibility(askpass_handler_code) {
Ok(_) => {
log::info!("Askpass handler initialized");
}
Err(e) => {
log::warn!("Failed to initialize askpass handler: {:?}", e);
}
}
}
// Sync R's options(width) with the current terminal width.
// Dynamic resize is handled by the idle callback above.
if self.config.r.auto_width {
sync_r_width();
}
// Set up the ReadConsole callback
arf_libr::set_read_console_callback(read_console_callback);
// Note: the Ctrl+C handler that forwards interrupts to R is installed
// in main() around R initialization (see install_r_interrupt_handler),
// so that startup profile evaluation is already covered.
// Run R's main loop - this doesn't return until EOF
unsafe {
arf_libr::run_r_mainloop();
}
// Sponge cleanup on exit: purge all remaining failed commands in the queue.
// Note: R's q() may terminate the process before this cleanup completes,
// so the most recent failed command might remain in history.
// The main value of sponge is purging OLD failed commands during the session.
REPL_STATE.with(|state| {
if let Some(ref mut repl_state) = *state.borrow_mut()
&& repl_state.forget_config.enabled
&& !repl_state.sponge_queue.is_empty()
{
for id_to_delete in repl_state.sponge_queue.drain_failed_ids() {
if let Some(store) = repl_state.r_history.store() {
let _ = store.delete(id_to_delete);
}
}
if let Some(store) = repl_state.r_history.store() {
let _ = store.sync();
}
}
});
REPL_STATE.with(|state| {
*state.borrow_mut() = None;
});
println!("\nGoodbye!");
Ok(())
}
}