Skip to content

Commit e6c15c1

Browse files
committed
feat(reply): add C++ snippet mode
1 parent 5de4871 commit e6c15c1

1 file changed

Lines changed: 291 additions & 4 deletions

File tree

src/core/ReplFlow.cpp

Lines changed: 291 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@
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

Comments
 (0)