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