4141#include < unordered_map>
4242#include < utility>
4343#include < vector>
44+ #include < cstdlib>
45+ #include < fstream>
46+ #include < sstream>
4447
4548#include < nlohmann/json.hpp>
4649
@@ -77,6 +80,7 @@ namespace
7780 struct TerminalRawMode
7881 {
7982 termios old{};
83+ bool available = false ;
8084 bool enabled = false ;
8185
8286 TerminalRawMode ()
@@ -91,6 +95,17 @@ namespace
9195 return ;
9296 }
9397
98+ available = true ;
99+ enable ();
100+ }
101+
102+ void enable ()
103+ {
104+ if (!available || enabled)
105+ {
106+ return ;
107+ }
108+
94109 termios raw = old;
95110
96111 raw.c_lflag &= static_cast <unsigned >(~(ICANON | ECHO ));
@@ -108,12 +123,20 @@ namespace
108123 }
109124 }
110125
111- ~TerminalRawMode ()
126+ void disable ()
112127 {
113- if (enabled)
128+ if (! enabled)
114129 {
115- tcsetattr ( STDIN_FILENO , TCSANOW , &old) ;
130+ return ;
116131 }
132+
133+ tcsetattr (STDIN_FILENO , TCSANOW , &old);
134+ enabled = false ;
135+ }
136+
137+ ~TerminalRawMode ()
138+ {
139+ disable ();
117140 }
118141 };
119142#endif
@@ -164,6 +187,10 @@ namespace
164187 << " Math:\n "
165188 << " <expr> Evaluate expression, for example 1+2*(3+4)\n "
166189 << " calc <expr> Evaluate expression explicitly\n "
190+ << " C++ snippets:\n "
191+ << " :cpp Enter C++ snippet mode\n "
192+ << " :run Run the current C++ snippet\n "
193+ << " :cancel Cancel C++ snippet mode\n "
167194 << " \n " ;
168195
169196 print_commands_from_dispatcher ();
@@ -180,6 +207,178 @@ namespace
180207 return vix::reply::trim_copy (s);
181208 }
182209
210+ static std::string shell_quote (const std::string &value)
211+ {
212+ #if defined(_WIN32)
213+ std::string out = " \" " ;
214+
215+ for (char c : value)
216+ {
217+ if (c == ' "' )
218+ {
219+ out += " \\\" " ;
220+ }
221+ else
222+ {
223+ out.push_back (c);
224+ }
225+ }
226+
227+ out += " \" " ;
228+ return out;
229+ #else
230+ std::string out = " '" ;
231+
232+ for (char c : value)
233+ {
234+ if (c == ' \' ' )
235+ {
236+ out += " '\\ ''" ;
237+ }
238+ else
239+ {
240+ out.push_back (c);
241+ }
242+ }
243+
244+ out += " '" ;
245+ return out;
246+ #endif
247+ }
248+
249+ static int count_cpp_braces_delta (const std::string &line)
250+ {
251+ int delta = 0 ;
252+ bool inString = false ;
253+ bool inChar = false ;
254+ bool escaping = false ;
255+
256+ for (char c : line)
257+ {
258+ if (escaping)
259+ {
260+ escaping = false ;
261+ continue ;
262+ }
263+
264+ if (c == ' \\ ' )
265+ {
266+ escaping = true ;
267+ continue ;
268+ }
269+
270+ if (inString)
271+ {
272+ if (c == ' "' )
273+ {
274+ inString = false ;
275+ }
276+
277+ continue ;
278+ }
279+
280+ if (inChar)
281+ {
282+ if (c == ' \' ' )
283+ {
284+ inChar = false ;
285+ }
286+
287+ continue ;
288+ }
289+
290+ if (c == ' "' )
291+ {
292+ inString = true ;
293+ continue ;
294+ }
295+
296+ if (c == ' \' ' )
297+ {
298+ inChar = true ;
299+ continue ;
300+ }
301+
302+ if (c == ' {' )
303+ {
304+ ++delta;
305+ }
306+ else if (c == ' }' )
307+ {
308+ --delta;
309+ }
310+ }
311+
312+ return delta;
313+ }
314+
315+ static bool looks_like_cpp_main (const std::string &line)
316+ {
317+ return line.find (" main(" ) != std::string::npos ||
318+ line.find (" main (" ) != std::string::npos;
319+ }
320+
321+ static int run_cpp_snippet (const std::vector<std::string> &lines)
322+ {
323+ if (lines.empty ())
324+ {
325+ std::cout << " error: no C++ code to run\n " ;
326+ return 1 ;
327+ }
328+
329+ std::error_code ec;
330+
331+ const fs::path root =
332+ fs::temp_directory_path (ec) / " vix-reply" ;
333+
334+ if (ec)
335+ {
336+ std::cout << " error: cannot resolve temporary directory: " << ec.message () << " \n " ;
337+ return 1 ;
338+ }
339+
340+ fs::create_directories (root, ec);
341+
342+ if (ec)
343+ {
344+ std::cout << " error: cannot create temporary directory: " << ec.message () << " \n " ;
345+ return 1 ;
346+ }
347+
348+ const auto now =
349+ std::chrono::steady_clock::now ().time_since_epoch ().count ();
350+
351+ const fs::path file =
352+ root / (" snippet-" + std::to_string (now) + " .cpp" );
353+
354+ {
355+ std::ofstream out (file, std::ios::trunc);
356+
357+ if (!out.is_open ())
358+ {
359+ std::cout << " error: cannot write C++ snippet: " << file.string () << " \n " ;
360+ return 1 ;
361+ }
362+
363+ for (const auto &line : lines)
364+ {
365+ out << line << ' \n ' ;
366+ }
367+ }
368+
369+ #if defined(_WIN32)
370+ const std::string command = " vix run " + shell_quote (file.string ());
371+ #else
372+ const std::string command = " vix run " + shell_quote (file.string ());
373+ #endif
374+
375+ const int rc = std::system (command.c_str ());
376+
377+ fs::remove (file, ec);
378+
379+ return rc;
380+ }
381+
183382 static std::string to_string (const vix::reply::api::CallValue &v)
184383 {
185384 if (v.is_string ())
@@ -1846,10 +2045,37 @@ namespace vix::reply
18462045 std::string histDraft;
18472046#endif
18482047
2048+ #ifndef _WIN32
2049+ auto run_cpp_snippet_from_repl = [&](const std::vector<std::string> &lines) -> int
2050+ {
2051+ rawMode.disable ();
2052+
2053+ const int rc = run_cpp_snippet (lines);
2054+
2055+ rawMode.enable ();
2056+
2057+ return rc;
2058+ };
2059+ #else
2060+ auto run_cpp_snippet_from_repl = [&](const std::vector<std::string> &lines) -> int
2061+ {
2062+ return run_cpp_snippet (lines);
2063+ };
2064+ #endif
2065+
2066+ bool cppMode = false ;
2067+ bool cppSeenMain = false ;
2068+ int cppBraceDepth = 0 ;
2069+ std::vector<std::string> cppLines;
2070+
18492071 while (true )
18502072 {
18512073 const fs::path cwd = fs::current_path ();
1852- const std::string prompt = make_prompt (cwd);
2074+
2075+ const std::string prompt =
2076+ cppMode
2077+ ? (cppLines.empty () ? " cpp> " : " ... " )
2078+ : make_prompt (cwd);
18532079
18542080 std::string line;
18552081
@@ -2150,6 +2376,67 @@ namespace vix::reply
21502376
21512377 history.add (line);
21522378
2379+ if (cppMode)
2380+ {
2381+ if (line == " :cancel" || line == " .cancel" )
2382+ {
2383+ cppMode = false ;
2384+ cppSeenMain = false ;
2385+ cppBraceDepth = 0 ;
2386+ cppLines.clear ();
2387+
2388+ std::cout << " C++ snippet cancelled\n " ;
2389+ continue ;
2390+ }
2391+
2392+ if (line == " :run" || line == " .run" )
2393+ {
2394+ const int rc = run_cpp_snippet_from_repl (cppLines);
2395+
2396+ cppMode = false ;
2397+ cppSeenMain = false ;
2398+ cppBraceDepth = 0 ;
2399+ cppLines.clear ();
2400+
2401+ (void )rc;
2402+ continue ;
2403+ }
2404+
2405+ cppLines.push_back (line);
2406+
2407+ if (looks_like_cpp_main (line))
2408+ {
2409+ cppSeenMain = true ;
2410+ }
2411+
2412+ cppBraceDepth += count_cpp_braces_delta (line);
2413+
2414+ if (cppSeenMain && cppBraceDepth <= 0 && line.find (' }' ) != std::string::npos)
2415+ {
2416+ const int rc = run_cpp_snippet_from_repl (cppLines);
2417+
2418+ cppMode = false ;
2419+ cppSeenMain = false ;
2420+ cppBraceDepth = 0 ;
2421+ cppLines.clear ();
2422+
2423+ (void )rc;
2424+ }
2425+
2426+ continue ;
2427+ }
2428+
2429+ if (line == " :cpp" || line == " .cpp" )
2430+ {
2431+ cppMode = true ;
2432+ cppSeenMain = false ;
2433+ cppBraceDepth = 0 ;
2434+ cppLines.clear ();
2435+
2436+ std::cout << " C++ mode. Type :run to execute or :cancel to exit.\n " ;
2437+ continue ;
2438+ }
2439+
21532440 if (auto assignment = parse_assignment (line))
21542441 {
21552442 const std::string &name = assignment->first ;
0 commit comments