1 #include "project.hpp"
2 #include "commands.hpp"
3 #include "config.hpp"
4 #include "directories.hpp"
5 #include "filesystem.hpp"
6 #include "menu.hpp"
7 #include "mutex.hpp"
8 #include "notebook.hpp"
9 #include "selection_dialog.hpp"
10 #include "terminal.hpp"
11 #include <fstream>
12 #ifdef JUCI_ENABLE_DEBUG
13 #include "debug_lldb.hpp"
14 #endif
15 #include "ctags.hpp"
16 #include "info.hpp"
17 #include "snippets.hpp"
18 #include "source_clang.hpp"
19 #include "usages_clang.hpp"
20 #include <future>
21 
22 boost::filesystem::path Project::debug_last_stop_file_path;
23 std::unordered_map<std::string, std::string> Project::run_arguments;
24 std::unordered_map<std::string, Project::DebugRunArguments> Project::debug_run_arguments;
25 std::atomic<bool> Project::compiling(false);
26 std::atomic<bool> Project::debugging(false);
27 std::pair<boost::filesystem::path, std::pair<int, int>> Project::debug_stop;
28 std::string Project::debug_status;
29 std::shared_ptr<Project::Base> Project::current;
30 std::unique_ptr<Project::DebugOptions> Project::Base::debug_options;
31 
get_preferably_view_folder()32 boost::filesystem::path Project::get_preferably_view_folder() {
33   boost::filesystem::path view_folder;
34   if(auto view = Notebook::get().get_current_view())
35     return view->file_path.parent_path();
36   else if(!Directories::get().path.empty())
37     return Directories::get().path;
38   else {
39     auto current_path = filesystem::get_current_path();
40     return !current_path.empty() ? current_path : boost::filesystem::path();
41   }
42 }
43 
get_preferably_directory_folder()44 boost::filesystem::path Project::get_preferably_directory_folder() {
45   if(!Directories::get().path.empty())
46     return Directories::get().path;
47   else if(auto view = Notebook::get().get_current_view())
48     return view->file_path.parent_path();
49   else {
50     auto current_path = filesystem::get_current_path();
51     return !current_path.empty() ? current_path : boost::filesystem::path();
52   }
53 }
54 
save_files(const boost::filesystem::path & path)55 void Project::save_files(const boost::filesystem::path &path) {
56   for(size_t c = 0; c < Notebook::get().size(); c++) {
57     auto view = Notebook::get().get_view(c);
58     if(view->get_buffer()->get_modified()) {
59       if(filesystem::file_in_path(view->file_path, path))
60         Notebook::get().save(c);
61     }
62   }
63 }
64 
on_save(size_t index)65 void Project::on_save(size_t index) {
66   auto view = Notebook::get().get_view(index);
67   if(!view)
68     return;
69 
70   if(view->file_path == Config::get().home_juci_path / "snippets.json") {
71     Snippets::get().load();
72     for(auto view : Notebook::get().get_views())
73       view->set_snippets();
74   }
75 
76   if(view->file_path == Config::get().home_juci_path / "commands.json")
77     Commands::get().load();
78 
79   boost::filesystem::path build_path;
80   if(view->language_id == "cmake") {
81     if(view->file_path.filename() == "CMakeLists.txt")
82       build_path = view->file_path;
83     else
84       build_path = filesystem::find_file_in_path_parents("CMakeLists.txt", view->file_path.parent_path());
85   }
86   else if(view->language_id == "meson") {
87     if(view->file_path.filename() == "meson.build")
88       build_path = view->file_path;
89     else
90       build_path = filesystem::find_file_in_path_parents("meson.build", view->file_path.parent_path());
91   }
92 
93   if(!build_path.empty()) {
94     auto build = Build::create(build_path);
95     if(dynamic_cast<CMakeBuild *>(build.get()) || dynamic_cast<MesonBuild *>(build.get())) {
96       build->update_default(true);
97       Usages::Clang::erase_all_caches_for_project(build->project_path, build->get_default_path());
98       boost::system::error_code ec;
99       if(boost::filesystem::exists(build->get_debug_path()), ec)
100         build->update_debug(true);
101 
102       for(size_t c = 0; c < Notebook::get().size(); c++) {
103         auto source_view = Notebook::get().get_view(c);
104         if(auto source_clang_view = dynamic_cast<Source::ClangView *>(source_view)) {
105           if(filesystem::file_in_path(source_clang_view->file_path, build->project_path))
106             source_clang_view->full_reparse_needed = true;
107         }
108       }
109     }
110   }
111 }
112 
debug_status_label()113 Gtk::Label &Project::debug_status_label() {
114   static Gtk::Label label;
115   return label;
116 }
117 
debug_update_status(const std::string & new_debug_status)118 void Project::debug_update_status(const std::string &new_debug_status) {
119   debug_status = new_debug_status;
120   if(debug_status.empty())
121     debug_status_label().set_text("");
122   else
123     debug_status_label().set_text(debug_status);
124   debug_activate_menu_items();
125 }
126 
debug_activate_menu_items()127 void Project::debug_activate_menu_items() {
128   auto &menu = Menu::get();
129   auto view = Notebook::get().get_current_view();
130   menu.actions["debug_stop"]->set_enabled(!debug_status.empty());
131   menu.actions["debug_kill"]->set_enabled(!debug_status.empty());
132   menu.actions["debug_step_over"]->set_enabled(!debug_status.empty());
133   menu.actions["debug_step_into"]->set_enabled(!debug_status.empty());
134   menu.actions["debug_step_out"]->set_enabled(!debug_status.empty());
135   menu.actions["debug_backtrace"]->set_enabled(!debug_status.empty());
136   menu.actions["debug_show_variables"]->set_enabled(!debug_status.empty());
137   menu.actions["debug_run_command"]->set_enabled(!debug_status.empty());
138   menu.actions["debug_toggle_breakpoint"]->set_enabled(view && view->toggle_breakpoint);
139   menu.actions["debug_goto_stop"]->set_enabled(!debug_status.empty());
140 }
141 
debug_update_stop()142 void Project::debug_update_stop() {
143   if(!debug_last_stop_file_path.empty()) {
144     for(size_t c = 0; c < Notebook::get().size(); c++) {
145       auto view = Notebook::get().get_view(c);
146       if(view->file_path == debug_last_stop_file_path) {
147         view->get_source_buffer()->remove_source_marks(view->get_buffer()->begin(), view->get_buffer()->end(), "debug_stop");
148         view->get_source_buffer()->remove_source_marks(view->get_buffer()->begin(), view->get_buffer()->end(), "debug_breakpoint_and_stop");
149         break;
150       }
151     }
152   }
153   //Add debug stop source mark
154   debug_last_stop_file_path.clear();
155   for(size_t c = 0; c < Notebook::get().size(); c++) {
156     auto view = Notebook::get().get_view(c);
157     if(view->file_path == debug_stop.first) {
158       if(debug_stop.second.first < view->get_buffer()->get_line_count()) {
159         auto iter = view->get_buffer()->get_iter_at_line(debug_stop.second.first);
160         gtk_source_buffer_create_source_mark(view->get_source_buffer()->gobj(), nullptr, "debug_stop", iter.gobj()); // Gsv::Buffer::create_source_mark is bugged
161         if(view->get_source_buffer()->get_source_marks_at_iter(iter, "debug_breakpoint").size() > 0)
162           gtk_source_buffer_create_source_mark(view->get_source_buffer()->gobj(), nullptr, "debug_breakpoint_and_stop", iter.gobj()); // Gsv::Buffer::create_source_mark is bugged
163         debug_last_stop_file_path = debug_stop.first;
164       }
165       break;
166     }
167   }
168 }
169 
create()170 std::shared_ptr<Project::Base> Project::create() {
171   std::unique_ptr<Project::Build> build;
172 
173   if(auto view = Notebook::get().get_current_view()) {
174     build = Build::create(view->file_path);
175     if(view->language_id == "markdown")
176       return std::shared_ptr<Project::Base>(new Project::Markdown(std::move(build)));
177     if(view->language_id == "js")
178       return std::shared_ptr<Project::Base>(new Project::JavaScript(std::move(build)));
179     if(view->language_id == "python")
180       return std::shared_ptr<Project::Base>(new Project::Python(std::move(build)));
181     if(view->language_id == "html")
182       return std::shared_ptr<Project::Base>(new Project::HTML(std::move(build)));
183     if(view->language_id == "go")
184       return std::shared_ptr<Project::Base>(new Project::Go(std::move(build)));
185     if(view->language_id == "julia")
186       return std::shared_ptr<Project::Base>(new Project::Julia(std::move(build)));
187   }
188   else
189     build = Build::create(Directories::get().path);
190 
191   if(dynamic_cast<CMakeBuild *>(build.get()) || dynamic_cast<MesonBuild *>(build.get()))
192     return std::shared_ptr<Project::Base>(new Project::Clang(std::move(build)));
193   if(dynamic_cast<CargoBuild *>(build.get()))
194     return std::shared_ptr<Project::Base>(new Project::Rust(std::move(build)));
195   if(dynamic_cast<NpmBuild *>(build.get()))
196     return std::shared_ptr<Project::Base>(new Project::JavaScript(std::move(build)));
197   if(dynamic_cast<PythonMain *>(build.get()))
198     return std::shared_ptr<Project::Base>(new Project::Python(std::move(build)));
199   if(dynamic_cast<GoBuild *>(build.get()))
200     return std::shared_ptr<Project::Base>(new Project::Go(std::move(build)));
201   return std::shared_ptr<Project::Base>(new Project::Base(std::move(build)));
202 }
203 
get_run_arguments()204 std::pair<std::string, std::string> Project::Base::get_run_arguments() {
205   Info::get().print("Could not find a supported project");
206   return {"", ""};
207 }
208 
compile()209 void Project::Base::compile() {
210   Info::get().print("Could not find a supported project");
211 }
212 
compile_and_run()213 void Project::Base::compile_and_run() {
214   Info::get().print("Could not find a supported project");
215 }
216 
recreate_build()217 void Project::Base::recreate_build() {
218   Info::get().print("Could not find a supported project");
219 }
220 
show_symbols()221 void Project::Base::show_symbols() {
222   Ctags ctags(get_preferably_view_folder());
223   if(!ctags) {
224     Info::get().print("No symbols found in current project");
225     return;
226   }
227 
228   auto view = Notebook::get().get_current_view();
229   if(view)
230     SelectionDialog::create(view, true, true);
231   else
232     SelectionDialog::create(true, true);
233 
234   std::vector<Source::Offset> rows;
235 
236   std::string line;
237   while(std::getline(ctags.output, line)) {
238     auto location = ctags.get_location(line, true);
239 
240     std::string row = location.file_path.string() + ":" + std::to_string(location.line + 1) + ": " + location.source;
241     rows.emplace_back(Source::Offset(location.line, location.index, location.file_path));
242     SelectionDialog::get()->add_row(row);
243   }
244 
245   if(rows.size() == 0)
246     return;
247   SelectionDialog::get()->on_select = [rows = std::move(rows), project_path = std::move(ctags.project_path)](unsigned int index, const std::string &text, bool hide_window) {
248     auto offset = rows[index];
249     auto full_path = project_path / offset.file_path;
250     boost::system::error_code ec;
251     if(!boost::filesystem::is_regular_file(full_path, ec))
252       return;
253     if(Notebook::get().open(full_path)) {
254       auto view = Notebook::get().get_current_view();
255       view->place_cursor_at_line_index(offset.line, offset.index);
256       view->scroll_to_cursor_delayed(true, false);
257     }
258   };
259   if(view)
260     view->hide_tooltips();
261   SelectionDialog::get()->show();
262 }
263 
debug_get_run_arguments()264 std::pair<std::string, std::string> Project::Base::debug_get_run_arguments() {
265   Info::get().print("Could not find a supported project");
266   return {"", ""};
267 }
268 
debug_compile_and_start()269 void Project::Base::debug_compile_and_start() {
270   Info::get().print("Could not find a supported project");
271 }
272 
debug_start(const std::string & command,const boost::filesystem::path & path,const std::string & remote_host)273 void Project::Base::debug_start(const std::string &command, const boost::filesystem::path &path, const std::string &remote_host) {
274   Info::get().print("Could not find a supported project");
275   Project::debugging = false;
276 }
277 
278 #ifdef JUCI_ENABLE_DEBUG
debug_get_run_arguments()279 std::pair<std::string, std::string> Project::LLDB::debug_get_run_arguments() {
280   auto debug_build_path = build->get_debug_path();
281   auto default_build_path = build->get_default_path();
282   if(debug_build_path.empty() || default_build_path.empty())
283     return {"", ""};
284 
285   auto project_path = build->project_path.string();
286   auto run_arguments_it = debug_run_arguments.find(project_path);
287   std::string arguments;
288   if(run_arguments_it != debug_run_arguments.end())
289     arguments = run_arguments_it->second.arguments;
290 
291   if(arguments.empty()) {
292     auto view = Notebook::get().get_current_view();
293     auto executable = build->get_executable(view ? view->file_path : Directories::get().path).string();
294 
295     if(!executable.empty()) {
296       size_t pos = executable.find(default_build_path.string());
297       if(pos != std::string::npos)
298         executable.replace(pos, default_build_path.string().size(), debug_build_path.string());
299       arguments = filesystem::escape_argument(filesystem::get_short_path(executable).string());
300     }
301     else
302       arguments = filesystem::escape_argument(filesystem::get_short_path(build->get_debug_path()).string());
303   }
304 
305   return {project_path, arguments};
306 }
307 
debug_get_options()308 Project::DebugOptions *Project::LLDB::debug_get_options() {
309   if(build->project_path.empty())
310     return nullptr;
311 
312   debug_options = std::make_unique<DebugOptions>();
313 
314   auto &arguments = Project::debug_run_arguments[build->project_path.string()];
315 
316   auto remote_enabled = Gtk::manage(new Gtk::CheckButton());
317   auto remote_host_port = Gtk::manage(new Gtk::Entry());
318   remote_enabled->set_active(arguments.remote_enabled);
319   remote_enabled->set_label("Enabled");
320   remote_enabled->signal_clicked().connect([remote_enabled, remote_host_port] {
321     remote_host_port->set_sensitive(remote_enabled->get_active());
322   });
323 
324   remote_host_port->set_sensitive(arguments.remote_enabled);
325   remote_host_port->set_text(arguments.remote_host_port);
326   remote_host_port->set_placeholder_text("host:port");
327   remote_host_port->signal_activate().connect([] {
328     debug_options->hide();
329   });
330 
331   auto self = this->shared_from_this();
332   debug_options->signal_hide().connect([self, remote_enabled, remote_host_port] {
333     auto &arguments = Project::debug_run_arguments[self->build->project_path.string()];
334     arguments.remote_enabled = remote_enabled->get_active();
335     arguments.remote_host_port = remote_host_port->get_text();
336   });
337 
338   auto remote_vbox = Gtk::manage(new Gtk::Box(Gtk::Orientation::ORIENTATION_VERTICAL));
339   remote_vbox->pack_start(*remote_enabled, true, true);
340   remote_vbox->pack_end(*remote_host_port, true, true);
341 
342   auto remote_frame = Gtk::manage(new Gtk::Frame());
343   remote_frame->set_label("Remote Debugging");
344   remote_frame->add(*remote_vbox);
345 
346   debug_options->vbox.pack_end(*remote_frame, true, true);
347 
348   return debug_options.get();
349 }
350 
debug_compile_and_start()351 void Project::LLDB::debug_compile_and_start() {
352   auto default_build_path = build->get_default_path();
353   if(default_build_path.empty() || !build->update_default())
354     return;
355   auto debug_build_path = build->get_debug_path();
356   if(debug_build_path.empty() || !build->update_debug())
357     return;
358 
359   auto run_arguments_it = debug_run_arguments.find(build->project_path.string());
360   std::string run_arguments;
361   std::string remote_host;
362   if(run_arguments_it != debug_run_arguments.end()) {
363     run_arguments = run_arguments_it->second.arguments;
364     if(run_arguments_it->second.remote_enabled)
365       remote_host = run_arguments_it->second.remote_host_port;
366   }
367 
368   if(run_arguments.empty()) {
369     auto view = Notebook::get().get_current_view();
370     run_arguments = build->get_executable(view ? view->file_path : Directories::get().path).string();
371     if(run_arguments.empty()) {
372       if(!build->is_valid())
373         Terminal::get().print("\e[31mError\e[m: build folder no longer valid, please rebuild project.\n", true);
374       else {
375         Terminal::get().print("\e[33mWarning\e[m: could not find executable.\n");
376         Terminal::get().print("Either use Project Set Run Arguments, or open a source file within a directory where an executable is defined.\n");
377       }
378       return;
379     }
380     size_t pos = run_arguments.find(default_build_path.string());
381     if(pos != std::string::npos)
382       run_arguments.replace(pos, default_build_path.string().size(), debug_build_path.string());
383     run_arguments = filesystem::escape_argument(filesystem::get_short_path(run_arguments).string());
384   }
385 
386   debugging = true;
387 
388   if(Config::get().terminal.clear_on_compile)
389     Terminal::get().clear();
390 
391   Terminal::get().print("\e[2mCompiling and debugging: " + run_arguments + "\e[m\n");
392   Terminal::get().async_process(build->get_compile_command(), debug_build_path, [self = this->shared_from_this(), run_arguments, project_path = build->project_path, remote_host](int exit_status) {
393     if(exit_status != EXIT_SUCCESS)
394       debugging = false;
395     else {
396       self->debug_start(run_arguments, project_path, remote_host);
397     }
398   });
399 }
400 
debug_start(const std::string & command,const boost::filesystem::path & path,const std::string & remote_host)401 void Project::LLDB::debug_start(const std::string &command, const boost::filesystem::path &path, const std::string &remote_host) {
402   std::vector<std::pair<boost::filesystem::path, int>> breakpoints;
403   for(size_t c = 0; c < Notebook::get().size(); c++) {
404     auto view = Notebook::get().get_view(c);
405     if(filesystem::file_in_path(view->file_path, path)) {
406       auto iter = view->get_buffer()->begin();
407       if(view->get_source_buffer()->get_source_marks_at_iter(iter, "debug_breakpoint").size() > 0)
408         breakpoints.emplace_back(view->file_path, iter.get_line() + 1);
409       while(view->get_source_buffer()->forward_iter_to_source_mark(iter, "debug_breakpoint"))
410         breakpoints.emplace_back(view->file_path, iter.get_line() + 1);
411     }
412   }
413 
414   static auto on_exit_it = Debug::LLDB::get().on_exit.end();
415   if(on_exit_it != Debug::LLDB::get().on_exit.end())
416     Debug::LLDB::get().on_exit.erase(on_exit_it);
417   Debug::LLDB::get().on_exit.emplace_back([self = shared_from_this(), command](int exit_status) {
418     debugging = false;
419     Terminal::get().async_print("\e[2m" + command + " returned: " + (exit_status == 0 ? "\e[32m" : "\e[31m") + std::to_string(exit_status) + "\e[m\n");
420     self->dispatcher.post([] {
421       debug_update_status("");
422     });
423   });
424   on_exit_it = std::prev(Debug::LLDB::get().on_exit.end());
425 
426   static auto on_event_it = Debug::LLDB::get().on_event.end();
427   if(on_event_it != Debug::LLDB::get().on_event.end())
428     Debug::LLDB::get().on_event.erase(on_event_it);
429   Debug::LLDB::get().on_event.emplace_back([self = shared_from_this()](const lldb::SBEvent &event) {
430     std::string status;
431     boost::filesystem::path stop_path;
432     unsigned stop_line = 0, stop_column = 0;
433 
434     LockGuard lock(Debug::LLDB::get().mutex);
435     auto process = lldb::SBProcess::GetProcessFromEvent(event);
436     auto state = lldb::SBProcess::GetStateFromEvent(event);
437     lldb::SBStream stream;
438     event.GetDescription(stream);
439     std::string event_desc = stream.GetData();
440     event_desc.pop_back();
441     auto pos = event_desc.rfind(" = ");
442     if(pos != std::string::npos && pos + 3 < event_desc.size())
443       status = event_desc.substr(pos + 3);
444     if(state == lldb::StateType::eStateStopped) {
445       char buffer[100];
446       auto thread = process.GetSelectedThread();
447       auto n = thread.GetStopDescription(buffer, 100); // Returns number of bytes read. Might include null termination... Although maybe on newer versions only.
448       if(n > 0)
449         status += " (" + std::string(buffer, n <= 100 ? (buffer[n - 1] == '\0' ? n - 1 : n) : 100) + ")";
450       auto line_entry = thread.GetSelectedFrame().GetLineEntry();
451       if(line_entry.IsValid()) {
452         lldb::SBStream stream;
453         line_entry.GetFileSpec().GetDescription(stream);
454         auto line = line_entry.GetLine();
455         status += " " + boost::filesystem::path(stream.GetData()).filename().string() + ":" + std::to_string(line);
456         auto column = line_entry.GetColumn();
457         if(column == 0)
458           column = 1;
459         stop_path = filesystem::get_normal_path(stream.GetData());
460         stop_line = line - 1;
461         stop_column = column - 1;
462       }
463     }
464 
465     self->dispatcher.post([status = std::move(status), stop_path = std::move(stop_path), stop_line, stop_column] {
466       debug_update_status(status);
467       Project::debug_stop.first = stop_path;
468       Project::debug_stop.second.first = stop_line;
469       Project::debug_stop.second.second = stop_column;
470       debug_update_stop();
471 
472       if(Config::get().source.debug_place_cursor_at_stop && !stop_path.empty()) {
473         if(Notebook::get().open(stop_path)) {
474           auto view = Notebook::get().get_current_view();
475           view->place_cursor_at_line_index(stop_line, stop_column);
476           view->scroll_to_cursor_delayed(true, false);
477         }
478       }
479       else if(auto view = Notebook::get().get_current_view())
480         view->get_buffer()->place_cursor(view->get_buffer()->get_insert()->get_iter());
481     });
482   });
483   on_event_it = std::prev(Debug::LLDB::get().on_event.end());
484 
485   std::vector<std::string> startup_commands;
486   if(dynamic_cast<CargoBuild *>(build.get())) {
487     auto sysroot = filesystem::get_rust_sysroot_path().string();
488     if(!sysroot.empty()) {
489       std::string line;
490       std::ifstream input(sysroot + "/lib/rustlib/etc/lldb_commands", std::ios::binary);
491       if(input) {
492         startup_commands.emplace_back("command script import \"" + sysroot + "/lib/rustlib/etc/lldb_lookup.py\"");
493         while(std::getline(input, line))
494           startup_commands.emplace_back(line);
495       }
496     }
497   }
498   Debug::LLDB::get().start(command, path, breakpoints, startup_commands, remote_host);
499 }
500 
debug_continue()501 void Project::LLDB::debug_continue() {
502   Debug::LLDB::get().continue_debug();
503 }
504 
debug_stop()505 void Project::LLDB::debug_stop() {
506   if(debugging)
507     Debug::LLDB::get().stop();
508 }
509 
debug_kill()510 void Project::LLDB::debug_kill() {
511   if(debugging)
512     Debug::LLDB::get().kill();
513 }
514 
debug_step_over()515 void Project::LLDB::debug_step_over() {
516   if(debugging)
517     Debug::LLDB::get().step_over();
518 }
519 
debug_step_into()520 void Project::LLDB::debug_step_into() {
521   if(debugging)
522     Debug::LLDB::get().step_into();
523 }
524 
debug_step_out()525 void Project::LLDB::debug_step_out() {
526   if(debugging)
527     Debug::LLDB::get().step_out();
528 }
529 
debug_backtrace()530 void Project::LLDB::debug_backtrace() {
531   if(debugging) {
532     auto view = Notebook::get().get_current_view();
533     auto frames = Debug::LLDB::get().get_backtrace();
534 
535     if(frames.size() == 0) {
536       Info::get().print("No backtrace found");
537       return;
538     }
539 
540     if(view)
541       SelectionDialog::create(view, true, true);
542     else
543       SelectionDialog::create(true, true);
544 
545     bool cursor_set = false;
546     for(auto &frame : frames) {
547       std::string row = "<i>" + frame.module_filename + "</i>";
548 
549       //Shorten frame.function_name if it is too long
550       if(frame.function_name.size() > 120) {
551         frame.function_name = frame.function_name.substr(0, 58) + "...." + frame.function_name.substr(frame.function_name.size() - 58);
552       }
553       if(frame.file_path.empty())
554         row += " - " + Glib::Markup::escape_text(frame.function_name);
555       else {
556         auto file_path = frame.file_path.filename().string();
557         row += ":<b>" + Glib::Markup::escape_text(file_path) + ":" + std::to_string(frame.line_nr) + "</b> - " + Glib::Markup::escape_text(frame.function_name);
558       }
559       SelectionDialog::get()->add_row(row);
560       if(!cursor_set && view && frame.file_path == view->file_path) {
561         SelectionDialog::get()->set_cursor_at_last_row();
562         cursor_set = true;
563       }
564     }
565 
566     SelectionDialog::get()->on_select = [frames = std::move(frames)](unsigned int index, const std::string &text, bool hide_window) {
567       auto &frame = frames[index];
568       if(!frame.file_path.empty()) {
569         if(Notebook::get().open(frame.file_path)) {
570           Debug::LLDB::get().select_frame(frame.index);
571           auto view = Notebook::get().get_current_view();
572           view->place_cursor_at_line_index(frame.line_nr - 1, frame.line_index - 1);
573           view->scroll_to_cursor_delayed(true, true);
574         }
575       }
576     };
577     if(view)
578       view->hide_tooltips();
579     SelectionDialog::get()->show();
580   }
581 }
582 
debug_show_variables()583 void Project::LLDB::debug_show_variables() {
584   if(debugging) {
585     auto view = Notebook::get().get_current_view();
586     auto variables = std::make_shared<std::vector<Debug::LLDB::Variable>>(Debug::LLDB::get().get_variables());
587 
588     if(variables->size() == 0) {
589       Info::get().print("No variables found");
590       return;
591     }
592 
593     if(view)
594       SelectionDialog::create(view, true, true);
595     else
596       SelectionDialog::create(true, true);
597 
598     for(auto &variable : *variables) {
599       std::string row = "#" + std::to_string(variable.thread_index_id) + ":#" + std::to_string(variable.frame_index) + ":" + variable.file_path.filename().string() + ":" + std::to_string(variable.line_nr) + " - <b>" + Glib::Markup::escape_text(variable.name) + "</b>";
600 
601       SelectionDialog::get()->add_row(row);
602     }
603 
604     SelectionDialog::get()->on_select = [variables](unsigned int index, const std::string &text, bool hide_window) {
605       auto &variable = (*variables)[index];
606       Debug::LLDB::get().select_frame(variable.frame_index, variable.thread_index_id);
607       if(!variable.file_path.empty()) {
608         if(Notebook::get().open(variable.file_path)) {
609           auto view = Notebook::get().get_current_view();
610           view->place_cursor_at_line_index(variable.line_nr - 1, variable.line_index - 1);
611           view->scroll_to_cursor_delayed(true, true);
612         }
613       }
614       if(!variable.declaration_found)
615         Info::get().print("Debugger did not find declaration for the variable: " + variable.name);
616     };
617 
618     SelectionDialog::get()->on_hide = [self = this->shared_from_this()]() {
619       self->debug_variable_tooltips.hide();
620       self->debug_variable_tooltips.clear();
621     };
622 
623     SelectionDialog::get()->on_change = [self = this->shared_from_this(), variables, view](boost::optional<unsigned int> index, const std::string &text) {
624       if(!index) {
625         self->debug_variable_tooltips.hide();
626         return;
627       }
628       self->debug_variable_tooltips.clear();
629 
630       auto set_tooltip_buffer = [variables, index](Tooltip &tooltip) {
631         auto &variable = (*variables)[*index];
632 
633         Glib::ustring value = variable.get_value();
634         if(!value.empty()) {
635           Glib::ustring::iterator iter;
636           while(!value.validate(iter)) {
637             auto next_char_iter = iter;
638             next_char_iter++;
639             value.replace(iter, next_char_iter, "?");
640           }
641           tooltip.insert_code(value.substr(0, value.size() - 1));
642         }
643       };
644       if(view) {
645         auto iter = view->get_buffer()->get_insert()->get_iter();
646         self->debug_variable_tooltips.emplace_back(view, iter, iter, std::move(set_tooltip_buffer));
647       }
648       else
649         self->debug_variable_tooltips.emplace_back(std::move(set_tooltip_buffer));
650 
651       self->debug_variable_tooltips.show(true);
652     };
653 
654     if(view)
655       view->hide_tooltips();
656     SelectionDialog::get()->show();
657   }
658 }
659 
debug_run_command(const std::string & command)660 void Project::LLDB::debug_run_command(const std::string &command) {
661   if(debugging) {
662     auto command_return = Debug::LLDB::get().run_command(command);
663     Terminal::get().async_print(std::move(command_return.first));
664     Terminal::get().async_print(std::move(command_return.second), true);
665   }
666 }
667 
debug_add_breakpoint(const boost::filesystem::path & file_path,int line_nr)668 void Project::LLDB::debug_add_breakpoint(const boost::filesystem::path &file_path, int line_nr) {
669   Debug::LLDB::get().add_breakpoint(file_path, line_nr);
670 }
671 
debug_remove_breakpoint(const boost::filesystem::path & file_path,int line_nr,int line_count)672 void Project::LLDB::debug_remove_breakpoint(const boost::filesystem::path &file_path, int line_nr, int line_count) {
673   Debug::LLDB::get().remove_breakpoint(file_path, line_nr, line_count);
674 }
675 
debug_is_running()676 bool Project::LLDB::debug_is_running() {
677   return Debug::LLDB::get().is_running();
678 }
679 
debug_write(const std::string & buffer)680 void Project::LLDB::debug_write(const std::string &buffer) {
681   Debug::LLDB::get().write(buffer);
682 }
683 #endif
684 
get_run_arguments()685 std::pair<std::string, std::string> Project::Clang::get_run_arguments() {
686   auto build_path = build->get_default_path();
687   if(build_path.empty())
688     return {"", ""};
689 
690   auto project_path = build->project_path.string();
691   auto run_arguments_it = run_arguments.find(project_path);
692   std::string arguments;
693   if(run_arguments_it != run_arguments.end())
694     arguments = run_arguments_it->second;
695 
696   if(arguments.empty()) {
697     auto view = Notebook::get().get_current_view();
698     auto executable = build->get_executable(view ? view->file_path : Directories::get().path);
699 
700     if(!executable.empty())
701       arguments = filesystem::escape_argument(filesystem::get_short_path(executable).string());
702     else
703       arguments = filesystem::escape_argument(filesystem::get_short_path(build->get_default_path()).string());
704   }
705 
706   return {project_path, arguments};
707 }
708 
compile()709 void Project::Clang::compile() {
710   auto default_build_path = build->get_default_path();
711   if(default_build_path.empty() || !build->update_default())
712     return;
713 
714   compiling = true;
715 
716   if(Config::get().terminal.clear_on_compile)
717     Terminal::get().clear();
718 
719   Terminal::get().print("\e[2mCompiling project: " + filesystem::get_short_path(build->project_path).string() + "\e[m\n");
720   Terminal::get().async_process(build->get_compile_command(), default_build_path, [](int exit_status) {
721     compiling = false;
722   });
723 }
724 
compile_and_run()725 void Project::Clang::compile_and_run() {
726   auto default_build_path = build->get_default_path();
727   if(default_build_path.empty() || !build->update_default())
728     return;
729 
730   auto project_path = build->project_path;
731 
732   auto run_arguments_it = run_arguments.find(project_path.string());
733   std::string arguments;
734   if(run_arguments_it != run_arguments.end())
735     arguments = run_arguments_it->second;
736 
737   if(arguments.empty()) {
738     auto view = Notebook::get().get_current_view();
739     auto executable = build->get_executable(view ? view->file_path : Directories::get().path);
740     if(executable.empty()) {
741       if(!build->is_valid())
742         Terminal::get().print("\e[31mError\e[m: build folder no longer valid, please rebuild project.\n", true);
743       else {
744         Terminal::get().print("\e[33mWarning\e[m: could not find executable.\n");
745         Terminal::get().print("Either use Project Set Run Arguments, or open a source file within a directory where an executable is defined.\n");
746       }
747       return;
748     }
749     arguments = filesystem::escape_argument(filesystem::get_short_path(executable).string());
750   }
751 
752   compiling = true;
753 
754   if(Config::get().terminal.clear_on_compile)
755     Terminal::get().clear();
756 
757   Terminal::get().print("\e[2mCompiling and running: " + arguments + "\e[m\n");
758   Terminal::get().async_process(build->get_compile_command(), default_build_path, [arguments, project_path](int exit_status) {
759     compiling = false;
760     if(exit_status == 0) {
761       Terminal::get().async_process(arguments, project_path, [arguments](int exit_status) {
762         Terminal::get().print("\e[2m" + arguments + " returned: " + (exit_status == 0 ? "\e[32m" : "\e[31m") + std::to_string(exit_status) + "\e[m\n");
763       });
764     }
765   });
766 }
767 
recreate_build()768 void Project::Clang::recreate_build() {
769   if(build->project_path.empty())
770     return;
771   auto default_build_path = build->get_default_path();
772   if(default_build_path.empty())
773     return;
774 
775   auto debug_build_path = build->get_debug_path();
776   boost::system::error_code ec;
777   bool has_default_build = boost::filesystem::exists(default_build_path, ec);
778   bool has_debug_build = !debug_build_path.empty() && boost::filesystem::exists(debug_build_path, ec);
779 
780   if(has_default_build || has_debug_build) {
781     Gtk::MessageDialog dialog(*static_cast<Gtk::Window *>(Notebook::get().get_toplevel()), "Recreate Build?", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO);
782     Gtk::Image image;
783     image.set_from_icon_name("dialog-question", Gtk::BuiltinIconSize::ICON_SIZE_DIALOG);
784     dialog.set_image(image);
785     dialog.set_default_response(Gtk::RESPONSE_YES);
786     std::string message = "Are you sure you want to recreate ";
787     if(has_default_build)
788       message += filesystem::get_short_path(default_build_path).string();
789     if(has_debug_build) {
790       if(has_default_build)
791         message += " and ";
792       message += filesystem::get_short_path(debug_build_path).string();
793     }
794     dialog.set_secondary_text(message + "?");
795     dialog.show_all();
796     if(dialog.run() != Gtk::RESPONSE_YES)
797       return;
798     Usages::Clang::erase_all_caches_for_project(build->project_path, default_build_path);
799     try {
800       if(has_default_build) {
801         std::vector<boost::filesystem::path> paths;
802         for(boost::filesystem::directory_iterator it(default_build_path), end; it != end; ++it)
803           paths.emplace_back(*it);
804         for(auto &path : paths)
805           boost::filesystem::remove_all(path);
806       }
807       if(has_debug_build && boost::filesystem::exists(debug_build_path)) {
808         std::vector<boost::filesystem::path> paths;
809         for(boost::filesystem::directory_iterator it(debug_build_path), end; it != end; ++it)
810           paths.emplace_back(*it);
811         for(auto &path : paths)
812           boost::filesystem::remove_all(path);
813       }
814     }
815     catch(const std::exception &e) {
816       Terminal::get().print(std::string("\e[31mError\e[m: could not remove build: ") + e.what() + "\n", true);
817       return;
818     }
819   }
820 
821   build->update_default(true);
822   if(has_debug_build)
823     build->update_debug(true);
824 
825   for(size_t c = 0; c < Notebook::get().size(); c++) {
826     auto source_view = Notebook::get().get_view(c);
827     if(auto source_clang_view = dynamic_cast<Source::ClangView *>(source_view)) {
828       if(filesystem::file_in_path(source_clang_view->file_path, build->project_path))
829         source_clang_view->full_reparse_needed = true;
830     }
831   }
832 
833   if(auto view = Notebook::get().get_current_view()) {
834     if(view->full_reparse_needed)
835       view->full_reparse();
836   }
837 }
838 
839 
compile_and_run()840 void Project::Markdown::compile_and_run() {
841   if(auto view = Notebook::get().get_current_view()) {
842     auto command = Config::get().project.markdown_command + ' ' + filesystem::escape_argument(filesystem::get_short_path(view->file_path).string());
843     Terminal::get().async_process(
844         command, "", [command](int exit_status) {
845           if(exit_status == 127)
846             Terminal::get().print("\e[31mError\e[m: executable not found: " + command + "\n", true);
847         },
848         true);
849   }
850 }
851 
compile_and_run()852 void Project::Python::compile_and_run() {
853   std::string command = Config::get().project.python_command + ' ';
854   boost::filesystem::path path;
855   if(dynamic_cast<PythonMain *>(build.get())) {
856     command += filesystem::get_short_path(build->project_path).string();
857     path = build->project_path;
858   }
859   else if(auto view = Notebook::get().get_current_view()) {
860     command += filesystem::escape_argument(filesystem::get_short_path(view->file_path).string());
861     path = view->file_path.parent_path();
862   }
863   else
864     return;
865 
866   if(Config::get().terminal.clear_on_compile)
867     Terminal::get().clear();
868 
869   Terminal::get().print("\e[2mRunning: " + command + "\e[m\n");
870   Terminal::get().async_process(command, path, [command](int exit_status) {
871     Terminal::get().print("\e[2m" + command + " returned: " + (exit_status == 0 ? "\e[32m" : "\e[31m") + std::to_string(exit_status) + "\e[m\n");
872   });
873 }
874 
compile_and_run()875 void Project::JavaScript::compile_and_run() {
876   std::string command;
877   boost::filesystem::path path;
878   if(dynamic_cast<NpmBuild *>(build.get())) {
879     command = "npm start";
880     path = build->project_path;
881   }
882   else if(auto view = Notebook::get().get_current_view()) {
883     command = "node " + filesystem::escape_argument(filesystem::get_short_path(view->file_path).string());
884     path = view->file_path.parent_path();
885   }
886   else
887     return;
888 
889   if(Config::get().terminal.clear_on_compile)
890     Terminal::get().clear();
891 
892   Terminal::get().print("\e[2mRunning: " + command + "\e[m\n");
893   Terminal::get().async_process(command, path, [command](int exit_status) {
894     Terminal::get().print("\e[2m" + command + " returned: " + (exit_status == 0 ? "\e[32m" : "\e[31m") + std::to_string(exit_status) + "\e[m\n");
895   });
896 }
897 
compile_and_run()898 void Project::HTML::compile_and_run() {
899   if(dynamic_cast<NpmBuild *>(build.get())) {
900     std::string command = "npm start";
901 
902     if(Config::get().terminal.clear_on_compile)
903       Terminal::get().clear();
904 
905     Terminal::get().print("\e[2mRunning: " + command + "\e[m\n");
906     Terminal::get().async_process(command, build->project_path, [command](int exit_status) {
907       Terminal::get().print("\e[2m" + command + " returned: " + (exit_status == 0 ? "\e[32m" : "\e[31m") + std::to_string(exit_status) + "\e[m\n");
908     });
909   }
910   else if(auto view = Notebook::get().get_current_view())
911     Notebook::get().open_uri(std::string("file://") + view->file_path.string());
912 }
913 
get_run_arguments()914 std::pair<std::string, std::string> Project::Rust::get_run_arguments() {
915   auto project_path = build->project_path.string();
916   auto run_arguments_it = run_arguments.find(project_path);
917   std::string arguments;
918   if(run_arguments_it != run_arguments.end())
919     arguments = run_arguments_it->second;
920 
921   if(arguments.empty())
922     arguments = filesystem::escape_argument(filesystem::get_short_path(build->get_executable(project_path)).string());
923 
924   return {project_path, arguments};
925 }
926 
compile()927 void Project::Rust::compile() {
928   compiling = true;
929 
930   if(Config::get().terminal.clear_on_compile)
931     Terminal::get().clear();
932 
933   Terminal::get().print("\e[2mCompiling project: " + filesystem::get_short_path(build->project_path).string() + "\e[m\n");
934 
935   Terminal::get().async_process(build->get_compile_command(), build->project_path, [](int exit_status) {
936     compiling = false;
937   });
938 }
939 
compile_and_run()940 void Project::Rust::compile_and_run() {
941   compiling = true;
942 
943   if(Config::get().terminal.clear_on_compile)
944     Terminal::get().clear();
945 
946   auto arguments = get_run_arguments().second;
947   Terminal::get().print("\e[2mCompiling and running: " + arguments + "\e[m\n");
948 
949   auto self = this->shared_from_this();
950   Terminal::get().async_process(build->get_compile_command(), build->project_path, [self, arguments = std::move(arguments)](int exit_status) {
951     compiling = false;
952     if(exit_status == 0) {
953       Terminal::get().async_process(arguments, self->build->project_path, [arguments](int exit_status) {
954         Terminal::get().print("\e[2m" + arguments + " returned: " + (exit_status == 0 ? "\e[32m" : "\e[31m") + std::to_string(exit_status) + "\e[m\n");
955       });
956     }
957   });
958 }
959 
compile_and_run()960 void Project::Go::compile_and_run() {
961   std::string command;
962   boost::filesystem::path path;
963   if(dynamic_cast<GoBuild *>(build.get())) {
964     command = "go run .";
965     path = build->project_path;
966   }
967   else if(auto view = Notebook::get().get_current_view()) {
968     command = "go run " + filesystem::escape_argument(filesystem::get_short_path(view->file_path).string());
969     path = view->file_path.parent_path();
970   }
971   else
972     return;
973 
974   if(Config::get().terminal.clear_on_compile)
975     Terminal::get().clear();
976 
977   Terminal::get().print("\e[2mRunning: " + command + "\e[m\n");
978   Terminal::get().async_process(command, path, [command](int exit_status) {
979     Terminal::get().print("\e[2m" + command + " returned: " + (exit_status == 0 ? "\e[32m" : "\e[31m") + std::to_string(exit_status) + "\e[m\n");
980   });
981 }
982 
compile_and_run()983 void Project::Julia::compile_and_run() {
984   if(auto view = Notebook::get().get_current_view()) {
985     auto command = "julia " + filesystem::escape_argument(filesystem::get_short_path(Notebook::get().get_current_view()->file_path).string());
986     auto path = view->file_path.parent_path();
987 
988     if(Config::get().terminal.clear_on_compile)
989       Terminal::get().clear();
990 
991     Terminal::get().print("\e[2mRunning: " + command + "\e[m\n");
992     Terminal::get().async_process(command, path, [command](int exit_status) {
993       Terminal::get().print("\e[2m" + command + " returned: " + (exit_status == 0 ? "\e[32m" : "\e[31m") + std::to_string(exit_status) + "\e[m\n");
994     });
995   }
996 }
997