From c2eff0a40ce538d90ce874e8b0c850bc3fad01d3 Mon Sep 17 00:00:00 2001 From: eidheim Date: Fri, 28 May 2021 12:48:34 +0200 Subject: [PATCH] Fixes #434 : added possibility to add custom commands in menu item juCi++, Commands --- CMakeLists.txt | 2 +- README.md | 8 +- src/CMakeLists.txt | 1 + src/commands.cpp | 53 ++++++++ src/commands.hpp | 29 +++++ src/compile_commands.cpp | 3 +- src/debug_lldb.cpp | 1 + src/directories.cpp | 2 +- src/files.hpp | 1 + src/filesystem.cpp | 10 ++ src/filesystem.hpp | 2 + src/menu.cpp | 4 + src/project.cpp | 252 ++++++++++++++++++++------------------ src/project.hpp | 6 +- src/source.cpp | 4 +- src/window.cpp | 97 ++++++++++++++- tests/filesystem_test.cpp | 8 ++ tests/stubs/project.cpp | 4 +- 18 files changed, 351 insertions(+), 136 deletions(-) create mode 100644 src/commands.cpp create mode 100644 src/commands.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 3fe3c00..782d6af 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.1) project(juci) -set(JUCI_VERSION "1.6.3") +set(JUCI_VERSION "1.6.3.1") set(CPACK_PACKAGE_NAME "jucipp") set(CPACK_PACKAGE_CONTACT "Ole Christian Eidheim ") diff --git a/README.md b/README.md index 88e5fa4..04855da 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,10 @@ IDE is instead integrated directly into juCi++. For effective development, juCi++ is primarily written for Unix/Linux systems. However, Windows users can use juCi++ through POSIX compatibility layers such as MSYS2. +## Installation + +See [installation guide](docs/install.md). + ## Features - Platform independent @@ -76,10 +80,6 @@ for planned features. -## Installation - -See [installation guide](docs/install.md). - ## Custom styling See [custom styling](docs/custom_styling.md). diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 14a0229..3585c7f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -2,6 +2,7 @@ set(JUCI_SHARED_FILES autocomplete.cpp cmake.cpp + commands.cpp compile_commands.cpp ctags.cpp dispatcher.cpp diff --git a/src/commands.cpp b/src/commands.cpp new file mode 100644 index 0000000..c58932f --- /dev/null +++ b/src/commands.cpp @@ -0,0 +1,53 @@ +#include "commands.hpp" +#include "config.hpp" +#include "filesystem.hpp" +#include "terminal.hpp" +#include + +void Commands::load() { + auto commands_file = Config::get().home_juci_path / "commands.json"; + + boost::system::error_code ec; + if(!boost::filesystem::exists(commands_file, ec)) + filesystem::write(commands_file, R"([ + { + "key": "1", + "path_comment": "Regular expression for which paths this command should apply", + "path": "^.*\\.json$", + "compile_comment": "Add compile command if a compilation step is needed prior to the run command. is set to the matching file or directory, and is set to the project directory if found or the matching file's directory.", + "compile": "", + "run_comment": " is set to the matching file or directory, and is set to the project directory if found or the matching file's directory", + "run": "echo && echo ", + "debug_comment": "Whether or not this command should run through debugger", + "debug": false, + "debug_remote_host": "" + } +] +)"); + + commands.clear(); + try { + boost::property_tree::ptree pt; + boost::property_tree::json_parser::read_json(commands_file.string(), pt); + for(auto command_it = pt.begin(); command_it != pt.end(); ++command_it) { + auto key_string = command_it->second.get("key"); + guint key = 0; + GdkModifierType modifier = static_cast(0); + if(!key_string.empty()) { + gtk_accelerator_parse(key_string.c_str(), &key, &modifier); + if(key == 0 && modifier == 0) + Terminal::get().async_print("\e[31mError\e[m: could not parse key string: " + key_string + "\n", true); + } + auto path = command_it->second.get("path", ""); + boost::optional regex; + if(!path.empty()) + regex = std::regex(path, std::regex::optimize); + commands.emplace_back(Command{key, modifier, std::move(regex), + command_it->second.get("compile", ""), command_it->second.get("run"), + command_it->second.get("debug", false), command_it->second.get("debug_remote_host", "")}); + } + } + catch(const std::exception &e) { + Terminal::get().async_print(std::string("\e[31mError\e[m: ") + e.what() + "\n", true); + } +} diff --git a/src/commands.hpp b/src/commands.hpp new file mode 100644 index 0000000..86847b7 --- /dev/null +++ b/src/commands.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include +#include +#include +#include +#include + +class Commands { +public: + class Command { + public: + guint key; + GdkModifierType modifier; + boost::optional path; + std::string compile; + std::string run; + bool debug; + std::string debug_remote_host; + }; + + static Commands &get() { + static Commands instance; + return instance; + } + + std::vector commands; + void load(); +}; diff --git a/src/compile_commands.cpp b/src/compile_commands.cpp index e5912b9..88e598b 100644 --- a/src/compile_commands.cpp +++ b/src/compile_commands.cpp @@ -1,6 +1,7 @@ #include "compile_commands.hpp" #include "clangmm.hpp" #include "config.hpp" +#include "filesystem.hpp" #include "terminal.hpp" #include "utility.hpp" #include @@ -107,7 +108,7 @@ CompileCommands::CompileCommands(const boost::filesystem::path &build_path) { if(parameter_start_pos != std::string::npos) add_parameter(); - commands.emplace_back(Command{directory, parameters, boost::filesystem::absolute(file, build_path)}); + commands.emplace_back(Command{directory, parameters, filesystem::get_absolute_path(file, build_path)}); } } catch(...) { diff --git a/src/debug_lldb.cpp b/src/debug_lldb.cpp index cb83b47..327f6c3 100644 --- a/src/debug_lldb.cpp +++ b/src/debug_lldb.cpp @@ -129,6 +129,7 @@ void Debug::LLDB::start(const std::string &command, const boost::filesystem::pat argv.emplace_back(argument.c_str()); argv.emplace_back(nullptr); + executable = filesystem::get_absolute_path(executable, path).string(); auto target = debugger->CreateTarget(executable.c_str()); if(!target.IsValid()) { Terminal::get().async_print("\e[31mError (debug)\e[m: Could not create debug target to: " + executable + '\n', true); diff --git a/src/directories.cpp b/src/directories.cpp index 3f3a915..fd154d7 100644 --- a/src/directories.cpp +++ b/src/directories.cpp @@ -438,7 +438,7 @@ Directories::Directories() : Gtk::ListViewText(1) { return; Gtk::MessageDialog dialog(*static_cast(get_toplevel()), "Delete!", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO); dialog.set_default_response(Gtk::RESPONSE_NO); - dialog.set_secondary_text("Are you sure you want to delete " + menu_popup_row_path.string() + "?"); + dialog.set_secondary_text("Are you sure you want to delete " + filesystem::get_short_path(menu_popup_row_path).string() + "?"); int result = dialog.run(); if(result == Gtk::RESPONSE_YES) { boost::system::error_code ec; diff --git a/src/files.hpp b/src/files.hpp index ffd6aaf..92cb638 100644 --- a/src/files.hpp +++ b/src/files.hpp @@ -126,6 +126,7 @@ const std::string default_config_file = "keybindings": { "preferences": "comma", "snippets": "", + "commands": "", "quit": "q", "file_new_file": "n", "file_new_folder": "n", diff --git a/src/filesystem.cpp b/src/filesystem.cpp index 7a6792f..76acacf 100644 --- a/src/filesystem.cpp +++ b/src/filesystem.cpp @@ -233,6 +233,16 @@ boost::filesystem::path filesystem::get_relative_path(const boost::filesystem::p return relative_path; } +boost::filesystem::path filesystem::get_absolute_path(const boost::filesystem::path &path, const boost::filesystem::path &base) noexcept { + boost::filesystem::path absolute_path; + for(auto path_it = path.begin(); path_it != path.end(); ++path_it) { + if(path_it == path.begin() && (!path.has_root_path() && *path_it != "~")) + absolute_path /= base; + absolute_path /= *path_it; + } + return absolute_path; +} + boost::filesystem::path filesystem::get_executable(const boost::filesystem::path &executable_name) noexcept { #if defined(__APPLE__) || defined(_WIN32) return executable_name; diff --git a/src/filesystem.hpp b/src/filesystem.hpp index 173faa3..4cd2135 100644 --- a/src/filesystem.hpp +++ b/src/filesystem.hpp @@ -35,6 +35,8 @@ public: static boost::filesystem::path get_relative_path(const boost::filesystem::path &path, const boost::filesystem::path &base) noexcept; + static boost::filesystem::path get_absolute_path(const boost::filesystem::path &path, const boost::filesystem::path &base) noexcept; + /// Return executable with latest version in filename on systems that is lacking executable_name symbolic link static boost::filesystem::path get_executable(const boost::filesystem::path &executable_name) noexcept; diff --git a/src/menu.cpp b/src/menu.cpp index 19aea24..24f335e 100644 --- a/src/menu.cpp +++ b/src/menu.cpp @@ -104,6 +104,10 @@ const Glib::ustring menu_xml = R"RAW( _Snippets app.snippets + + _Commands + app.commands +
diff --git a/src/project.cpp b/src/project.cpp index 8a89889..bcac224 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -1,4 +1,5 @@ #include "project.hpp" +#include "commands.hpp" #include "config.hpp" #include "directories.hpp" #include "filesystem.hpp" @@ -72,6 +73,9 @@ void Project::on_save(size_t index) { view->set_snippets(); } + if(view->file_path == Config::get().home_juci_path / "commands.json") + Commands::get().load(); + boost::filesystem::path build_path; if(view->language && view->language->get_id() == "cmake") { if(view->file_path.filename() == "CMakeLists.txt") @@ -259,10 +263,15 @@ std::pair Project::Base::debug_get_run_arguments() { return {"", ""}; } -void Project::Base::debug_start() { +void Project::Base::debug_compile_and_start() { Info::get().print("Could not find a supported project"); } +void Project::Base::debug_start(const std::string &command, const boost::filesystem::path &path, const std::string &remote_host) { + Info::get().print("Could not find a supported project"); + Project::debugging = false; +} + #ifdef JUCI_ENABLE_DEBUG std::pair Project::LLDB::debug_get_run_arguments() { auto debug_build_path = build->get_debug_path(); @@ -336,7 +345,7 @@ Project::DebugOptions *Project::LLDB::debug_get_options() { return debug_options.get(); } -void Project::LLDB::debug_start() { +void Project::LLDB::debug_compile_and_start() { auto default_build_path = build->get_default_path(); if(default_build_path.empty() || !build->update_default()) return; @@ -344,17 +353,19 @@ void Project::LLDB::debug_start() { if(debug_build_path.empty() || !build->update_debug()) return; - auto project_path = std::make_shared(build->project_path); - - auto run_arguments_it = debug_run_arguments.find(project_path->string()); - auto run_arguments = std::make_shared(); - if(run_arguments_it != debug_run_arguments.end()) - *run_arguments = run_arguments_it->second.arguments; + auto run_arguments_it = debug_run_arguments.find(build->project_path.string()); + std::string run_arguments; + std::string remote_host; + if(run_arguments_it != debug_run_arguments.end()) { + run_arguments = run_arguments_it->second.arguments; + if(run_arguments_it->second.remote_enabled) + remote_host = run_arguments_it->second.remote_host_port; + } - if(run_arguments->empty()) { + if(run_arguments.empty()) { auto view = Notebook::get().get_current_view(); - *run_arguments = build->get_executable(view ? view->file_path : Directories::get().path).string(); - if(run_arguments->empty()) { + run_arguments = build->get_executable(view ? view->file_path : Directories::get().path).string(); + if(run_arguments.empty()) { if(!build->is_valid()) Terminal::get().print("\e[31mError\e[m: build folder no longer valid, please rebuild project.\n", true); else { @@ -363,10 +374,10 @@ void Project::LLDB::debug_start() { } return; } - size_t pos = run_arguments->find(default_build_path.string()); + size_t pos = run_arguments.find(default_build_path.string()); if(pos != std::string::npos) - run_arguments->replace(pos, default_build_path.string().size(), debug_build_path.string()); - *run_arguments = filesystem::escape_argument(filesystem::get_short_path(*run_arguments).string()); + run_arguments.replace(pos, default_build_path.string().size(), debug_build_path.string()); + run_arguments = filesystem::escape_argument(filesystem::get_short_path(run_arguments).string()); } debugging = true; @@ -374,115 +385,114 @@ void Project::LLDB::debug_start() { if(Config::get().terminal.clear_on_compile) Terminal::get().clear(); - Terminal::get().print("\e[2mCompiling and debugging " + *run_arguments + "\e[m\n"); - Terminal::get().async_process(build->get_compile_command(), debug_build_path, [self = this->shared_from_this(), run_arguments, project_path](int exit_status) { + Terminal::get().print("\e[2mCompiling and debugging: " + run_arguments + "\e[m\n"); + 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) { if(exit_status != EXIT_SUCCESS) debugging = false; else { - std::vector> breakpoints; - for(size_t c = 0; c < Notebook::get().size(); c++) { - auto view = Notebook::get().get_view(c); - if(filesystem::file_in_path(view->file_path, *project_path)) { - auto iter = view->get_buffer()->begin(); - if(view->get_source_buffer()->get_source_marks_at_iter(iter, "debug_breakpoint").size() > 0) - breakpoints.emplace_back(view->file_path, iter.get_line() + 1); - while(view->get_source_buffer()->forward_iter_to_source_mark(iter, "debug_breakpoint")) - breakpoints.emplace_back(view->file_path, iter.get_line() + 1); - } - } + self->debug_start(run_arguments, project_path, remote_host); + } + }); +} - std::string remote_host; - auto debug_run_arguments_it = debug_run_arguments.find(project_path->string()); - if(debug_run_arguments_it != debug_run_arguments.end() && debug_run_arguments_it->second.remote_enabled) - remote_host = debug_run_arguments_it->second.remote_host_port; - - static auto on_exit_it = Debug::LLDB::get().on_exit.end(); - if(on_exit_it != Debug::LLDB::get().on_exit.end()) - Debug::LLDB::get().on_exit.erase(on_exit_it); - Debug::LLDB::get().on_exit.emplace_back([self, run_arguments](int exit_status) { - debugging = false; - Terminal::get().async_print("\e[2m" + *run_arguments + " returned: " + (exit_status == 0 ? "\e[32m" : "\e[31m") + std::to_string(exit_status) + "\e[m\n"); - self->dispatcher.post([] { - debug_update_status(""); - }); - }); - on_exit_it = std::prev(Debug::LLDB::get().on_exit.end()); - - static auto on_event_it = Debug::LLDB::get().on_event.end(); - if(on_event_it != Debug::LLDB::get().on_event.end()) - Debug::LLDB::get().on_event.erase(on_event_it); - Debug::LLDB::get().on_event.emplace_back([self](const lldb::SBEvent &event) { - std::string status; - boost::filesystem::path stop_path; - unsigned stop_line = 0, stop_column = 0; - - LockGuard lock(Debug::LLDB::get().mutex); - auto process = lldb::SBProcess::GetProcessFromEvent(event); - auto state = lldb::SBProcess::GetStateFromEvent(event); +void Project::LLDB::debug_start(const std::string &command, const boost::filesystem::path &path, const std::string &remote_host) { + std::vector> breakpoints; + for(size_t c = 0; c < Notebook::get().size(); c++) { + auto view = Notebook::get().get_view(c); + if(filesystem::file_in_path(view->file_path, path)) { + auto iter = view->get_buffer()->begin(); + if(view->get_source_buffer()->get_source_marks_at_iter(iter, "debug_breakpoint").size() > 0) + breakpoints.emplace_back(view->file_path, iter.get_line() + 1); + while(view->get_source_buffer()->forward_iter_to_source_mark(iter, "debug_breakpoint")) + breakpoints.emplace_back(view->file_path, iter.get_line() + 1); + } + } + + static auto on_exit_it = Debug::LLDB::get().on_exit.end(); + if(on_exit_it != Debug::LLDB::get().on_exit.end()) + Debug::LLDB::get().on_exit.erase(on_exit_it); + Debug::LLDB::get().on_exit.emplace_back([self = shared_from_this(), command](int exit_status) { + debugging = false; + Terminal::get().async_print("\e[2m" + command + " returned: " + (exit_status == 0 ? "\e[32m" : "\e[31m") + std::to_string(exit_status) + "\e[m\n"); + self->dispatcher.post([] { + debug_update_status(""); + }); + }); + on_exit_it = std::prev(Debug::LLDB::get().on_exit.end()); + + static auto on_event_it = Debug::LLDB::get().on_event.end(); + if(on_event_it != Debug::LLDB::get().on_event.end()) + Debug::LLDB::get().on_event.erase(on_event_it); + Debug::LLDB::get().on_event.emplace_back([self = shared_from_this()](const lldb::SBEvent &event) { + std::string status; + boost::filesystem::path stop_path; + unsigned stop_line = 0, stop_column = 0; + + LockGuard lock(Debug::LLDB::get().mutex); + auto process = lldb::SBProcess::GetProcessFromEvent(event); + auto state = lldb::SBProcess::GetStateFromEvent(event); + lldb::SBStream stream; + event.GetDescription(stream); + std::string event_desc = stream.GetData(); + event_desc.pop_back(); + auto pos = event_desc.rfind(" = "); + if(pos != std::string::npos && pos + 3 < event_desc.size()) + status = event_desc.substr(pos + 3); + if(state == lldb::StateType::eStateStopped) { + char buffer[100]; + auto thread = process.GetSelectedThread(); + auto n = thread.GetStopDescription(buffer, 100); // Returns number of bytes read. Might include null termination... Although maybe on newer versions only. + if(n > 0) + status += " (" + std::string(buffer, n <= 100 ? (buffer[n - 1] == '\0' ? n - 1 : n) : 100) + ")"; + auto line_entry = thread.GetSelectedFrame().GetLineEntry(); + if(line_entry.IsValid()) { lldb::SBStream stream; - event.GetDescription(stream); - std::string event_desc = stream.GetData(); - event_desc.pop_back(); - auto pos = event_desc.rfind(" = "); - if(pos != std::string::npos && pos + 3 < event_desc.size()) - status = event_desc.substr(pos + 3); - if(state == lldb::StateType::eStateStopped) { - char buffer[100]; - auto thread = process.GetSelectedThread(); - auto n = thread.GetStopDescription(buffer, 100); // Returns number of bytes read. Might include null termination... Although maybe on newer versions only. - if(n > 0) - status += " (" + std::string(buffer, n <= 100 ? (buffer[n - 1] == '\0' ? n - 1 : n) : 100) + ")"; - auto line_entry = thread.GetSelectedFrame().GetLineEntry(); - if(line_entry.IsValid()) { - lldb::SBStream stream; - line_entry.GetFileSpec().GetDescription(stream); - auto line = line_entry.GetLine(); - status += " " + boost::filesystem::path(stream.GetData()).filename().string() + ":" + std::to_string(line); - auto column = line_entry.GetColumn(); - if(column == 0) - column = 1; - stop_path = filesystem::get_normal_path(stream.GetData()); - stop_line = line - 1; - stop_column = column - 1; - } - } + line_entry.GetFileSpec().GetDescription(stream); + auto line = line_entry.GetLine(); + status += " " + boost::filesystem::path(stream.GetData()).filename().string() + ":" + std::to_string(line); + auto column = line_entry.GetColumn(); + if(column == 0) + column = 1; + stop_path = filesystem::get_normal_path(stream.GetData()); + stop_line = line - 1; + stop_column = column - 1; + } + } - self->dispatcher.post([status = std::move(status), stop_path = std::move(stop_path), stop_line, stop_column] { - debug_update_status(status); - Project::debug_stop.first = stop_path; - Project::debug_stop.second.first = stop_line; - Project::debug_stop.second.second = stop_column; - debug_update_stop(); - - if(Config::get().source.debug_place_cursor_at_stop && !stop_path.empty()) { - if(Notebook::get().open(stop_path)) { - auto view = Notebook::get().get_current_view(); - view->place_cursor_at_line_index(stop_line, stop_column); - view->scroll_to_cursor_delayed(true, false); - } - } - else if(auto view = Notebook::get().get_current_view()) - view->get_buffer()->place_cursor(view->get_buffer()->get_insert()->get_iter()); - }); - }); - on_event_it = std::prev(Debug::LLDB::get().on_event.end()); - - std::vector startup_commands; - if(dynamic_cast(self->build.get())) { - auto sysroot = filesystem::get_rust_sysroot_path().string(); - if(!sysroot.empty()) { - std::string line; - std::ifstream input(sysroot + "/lib/rustlib/etc/lldb_commands", std::ofstream::binary); - if(input) { - startup_commands.emplace_back("command script import \"" + sysroot + "/lib/rustlib/etc/lldb_lookup.py\""); - while(std::getline(input, line)) - startup_commands.emplace_back(line); - } + self->dispatcher.post([status = std::move(status), stop_path = std::move(stop_path), stop_line, stop_column] { + debug_update_status(status); + Project::debug_stop.first = stop_path; + Project::debug_stop.second.first = stop_line; + Project::debug_stop.second.second = stop_column; + debug_update_stop(); + + if(Config::get().source.debug_place_cursor_at_stop && !stop_path.empty()) { + if(Notebook::get().open(stop_path)) { + auto view = Notebook::get().get_current_view(); + view->place_cursor_at_line_index(stop_line, stop_column); + view->scroll_to_cursor_delayed(true, false); } } - Debug::LLDB::get().start(*run_arguments, *project_path, breakpoints, startup_commands, remote_host); - } + else if(auto view = Notebook::get().get_current_view()) + view->get_buffer()->place_cursor(view->get_buffer()->get_insert()->get_iter()); + }); }); + on_event_it = std::prev(Debug::LLDB::get().on_event.end()); + + std::vector startup_commands; + if(dynamic_cast(build.get())) { + auto sysroot = filesystem::get_rust_sysroot_path().string(); + if(!sysroot.empty()) { + std::string line; + std::ifstream input(sysroot + "/lib/rustlib/etc/lldb_commands", std::ofstream::binary); + if(input) { + startup_commands.emplace_back("command script import \"" + sysroot + "/lib/rustlib/etc/lldb_lookup.py\""); + while(std::getline(input, line)) + startup_commands.emplace_back(line); + } + } + } + Debug::LLDB::get().start(command, path, breakpoints, startup_commands, remote_host); } void Project::LLDB::debug_continue() { @@ -703,7 +713,7 @@ void Project::Clang::compile() { if(Config::get().terminal.clear_on_compile) Terminal::get().clear(); - Terminal::get().print("\e[2mCompiling project " + filesystem::get_short_path(build->project_path).string() + "\e[m\n"); + Terminal::get().print("\e[2mCompiling project: " + filesystem::get_short_path(build->project_path).string() + "\e[m\n"); Terminal::get().async_process(build->get_compile_command(), default_build_path, [](int exit_status) { compiling = false; }); @@ -741,7 +751,7 @@ void Project::Clang::compile_and_run() { if(Config::get().terminal.clear_on_compile) Terminal::get().clear(); - Terminal::get().print("\e[2mCompiling and running " + arguments + "\e[m\n"); + Terminal::get().print("\e[2mCompiling and running: " + arguments + "\e[m\n"); Terminal::get().async_process(build->get_compile_command(), default_build_path, [arguments, project_path](int exit_status) { compiling = false; if(exit_status == 0) { @@ -852,7 +862,7 @@ void Project::Python::compile_and_run() { if(Config::get().terminal.clear_on_compile) Terminal::get().clear(); - Terminal::get().print("\e[2mRunning " + command + "\e[m\n"); + Terminal::get().print("\e[2mRunning: " + command + "\e[m\n"); Terminal::get().async_process(command, path, [command](int exit_status) { Terminal::get().print("\e[2m" + command + " returned: " + (exit_status == 0 ? "\e[32m" : "\e[31m") + std::to_string(exit_status) + "\e[m\n"); }); @@ -878,7 +888,7 @@ void Project::JavaScript::compile_and_run() { if(Config::get().terminal.clear_on_compile) Terminal::get().clear(); - Terminal::get().print("\e[2mRunning " + command + "\e[m\n"); + Terminal::get().print("\e[2mRunning: " + command + "\e[m\n"); Terminal::get().async_process(command, path, [command](int exit_status) { Terminal::get().print("\e[2m" + command + " returned: " + (exit_status == 0 ? "\e[32m" : "\e[31m") + std::to_string(exit_status) + "\e[m\n"); }); @@ -891,7 +901,7 @@ void Project::HTML::compile_and_run() { if(Config::get().terminal.clear_on_compile) Terminal::get().clear(); - Terminal::get().print("\e[2mRunning " + command + "\e[m\n"); + Terminal::get().print("\e[2mRunning: " + command + "\e[m\n"); Terminal::get().async_process(command, build->project_path, [command](int exit_status) { Terminal::get().print("\e[2m" + command + " returned: " + (exit_status == 0 ? "\e[32m" : "\e[31m") + std::to_string(exit_status) + "\e[m\n"); }); @@ -919,7 +929,7 @@ void Project::Rust::compile() { if(Config::get().terminal.clear_on_compile) Terminal::get().clear(); - Terminal::get().print("\e[2mCompiling project " + filesystem::get_short_path(build->project_path).string() + "\e[m\n"); + Terminal::get().print("\e[2mCompiling project: " + filesystem::get_short_path(build->project_path).string() + "\e[m\n"); Terminal::get().async_process(build->get_compile_command(), build->project_path, [](int exit_status) { compiling = false; @@ -933,7 +943,7 @@ void Project::Rust::compile_and_run() { Terminal::get().clear(); auto arguments = get_run_arguments().second; - Terminal::get().print("\e[2mCompiling and running " + arguments + "\e[m\n"); + Terminal::get().print("\e[2mCompiling and running: " + arguments + "\e[m\n"); auto self = this->shared_from_this(); Terminal::get().async_process(build->get_compile_command(), build->project_path, [self, arguments = std::move(arguments)](int exit_status) { diff --git a/src/project.hpp b/src/project.hpp index 942c5a7..e5e3f94 100644 --- a/src/project.hpp +++ b/src/project.hpp @@ -64,7 +64,8 @@ namespace Project { virtual std::pair debug_get_run_arguments(); virtual Project::DebugOptions *debug_get_options() { return nullptr; } Tooltips debug_variable_tooltips; - virtual void debug_start(); + virtual void debug_compile_and_start(); + virtual void debug_start(const std::string &command, const boost::filesystem::path &path, const std::string &remote_host); virtual void debug_continue() {} virtual void debug_stop() {} virtual void debug_kill() {} @@ -85,7 +86,8 @@ namespace Project { #ifdef JUCI_ENABLE_DEBUG std::pair debug_get_run_arguments() override; Project::DebugOptions *debug_get_options() override; - void debug_start() override; + void debug_compile_and_start() override; + void debug_start(const std::string &command, const boost::filesystem::path &path, const std::string &remote_host) override; void debug_continue() override; void debug_stop() override; void debug_kill() override; diff --git a/src/source.cpp b/src/source.cpp index 0d9bdb9..96dacb5 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -2104,8 +2104,10 @@ bool Source::View::on_key_press_event(GdkEventKey *event) { { LockGuard lock(snippets_mutex); if(snippets) { + guint keyval_without_state; + gdk_keymap_translate_keyboard_state(gdk_keymap_get_default(), event->hardware_keycode, (GdkModifierType)0, 0, &keyval_without_state, nullptr, nullptr, nullptr); for(auto &snippet : *snippets) { - if(snippet.key == event->keyval && (snippet.modifier & event->state) == snippet.modifier) { + if((snippet.key == event->keyval || snippet.key == keyval_without_state) && (event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK | GDK_MOD1_MASK | GDK_META_MASK)) == snippet.modifier) { insert_snippet(get_buffer()->get_insert()->get_iter(), snippet.body); return true; } diff --git a/src/window.cpp b/src/window.cpp index 2850448..dc468fb 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -3,6 +3,7 @@ #ifdef JUCI_ENABLE_DEBUG #include "debug_lldb.hpp" #endif +#include "commands.hpp" #include "compile_commands.hpp" #include "dialog.hpp" #include "directories.hpp" @@ -15,6 +16,7 @@ #include "project.hpp" #include "selection_dialog.hpp" #include "terminal.hpp" +#include Window::Window() { Gsv::init(); @@ -166,6 +168,7 @@ Window::Window() { void Window::configure() { Config::get().load(); Snippets::get().load(); + Commands::get().load(); auto screen = get_screen(); static Glib::RefPtr css_provider_theme; @@ -284,6 +287,9 @@ void Window::set_menu_actions() { menu.add_action("snippets", []() { Notebook::get().open(Config::get().home_juci_path / "snippets.json"); }); + menu.add_action("commands", []() { + Notebook::get().open(Config::get().home_juci_path / "commands.json"); + }); menu.add_action("quit", [this]() { close(); }); @@ -1386,7 +1392,7 @@ void Window::set_menu_actions() { Project::current = Project::create(); if(Config::get().project.save_on_compile_or_run) - Project::save_files(Project::current->build->project_path); + Project::save_files(!Project::current->build->project_path.empty() ? Project::current->build->project_path : Project::get_preferably_view_folder()); Project::current->compile_and_run(); }); @@ -1399,7 +1405,7 @@ void Window::set_menu_actions() { Project::current = Project::create(); if(Config::get().project.save_on_compile_or_run) - Project::save_files(Project::current->build->project_path); + Project::save_files(!Project::current->build->project_path.empty() ? Project::current->build->project_path : Project::get_preferably_view_folder()); Project::current->compile(); }); @@ -1507,9 +1513,9 @@ void Window::set_menu_actions() { Project::current = Project::create(); if(Config::get().project.save_on_compile_or_run) - Project::save_files(Project::current->build->project_path); + Project::save_files(!Project::current->build->project_path.empty() ? Project::current->build->project_path : Project::get_preferably_view_folder()); - Project::current->debug_start(); + Project::current->debug_compile_and_start(); }); menu.add_action("debug_stop", []() { if(Project::current) @@ -1893,6 +1899,89 @@ void Window::add_widgets() { } bool Window::on_key_press_event(GdkEventKey *event) { + guint keyval_without_state; + gdk_keymap_translate_keyboard_state(gdk_keymap_get_default(), event->hardware_keycode, (GdkModifierType)0, 0, &keyval_without_state, nullptr, nullptr, nullptr); + for(auto &command : Commands::get().commands) { + if((command.key == event->keyval || command.key == keyval_without_state) && (event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK | GDK_MOD1_MASK | GDK_META_MASK)) == command.modifier) { + auto view = Notebook::get().get_current_view(); + auto view_folder = Project::get_preferably_view_folder(); + auto path = view ? view->file_path : view_folder; + if(command.path) { + std::smatch sm; + if(!std::regex_match(path.string(), sm, *command.path) && !std::regex_match(filesystem::get_short_path(path).string(), sm, *command.path)) + continue; + } + + auto project = Project::create(); + auto run_path = !project->build->project_path.empty() ? project->build->project_path : view_folder; + + auto compile = command.compile; + boost::replace_all(compile, "", filesystem::escape_argument(path.string())); + boost::replace_all(compile, "", filesystem::escape_argument(run_path.string())); + auto run = command.run; + boost::replace_all(run, "", filesystem::escape_argument(path.string())); + boost::replace_all(run, "", filesystem::escape_argument(run_path.string())); + + if(!compile.empty() || command.debug) { + if(Project::debugging && command.debug) // Possibly continue current debugging + break; + if(Project::compiling || Project::debugging) { + Info::get().print("Compile or debug in progress"); + break; + } + Project::current = project; + } + + if(Config::get().project.save_on_compile_or_run) + Project::save_files(run_path); + + if(Config::get().terminal.clear_on_run_command || (!compile.empty() && Config::get().terminal.clear_on_compile)) + Terminal::get().clear(); + + if(!compile.empty()) { + if(!project->build->get_default_path().empty()) + project->build->update_default(); + if(command.debug && !project->build->get_debug_path().empty()) + project->build->update_debug(); + + if(!command.debug) { + Project::compiling = true; + Terminal::get().print("\e[2mCompiling and running: " + run + "\e[m\n"); + Terminal::get().async_process(compile, run_path, [run, run_path](int exit_status) { + Project::compiling = false; + if(exit_status == 0) { + Terminal::get().async_process(run, run_path, [run](int exit_status) { + Terminal::get().print("\e[2m" + run + " returned: " + (exit_status == 0 ? "\e[32m" : "\e[31m") + std::to_string(exit_status) + "\e[m\n"); + }); + } + }); + } + else { // Debug + Project::debugging = true; + Terminal::get().print("\e[2mCompiling and debugging: " + run + "\e[m\n"); + Terminal::get().async_process(compile, run_path, [project = project->shared_from_this(), run, run_path, debug_remote_host = command.debug_remote_host](int exit_status) { + if(exit_status != EXIT_SUCCESS) + Project::debugging = false; + else + project->debug_start(run, run_path, debug_remote_host); + }); + } + } + else if(!command.debug) { + Terminal::get().async_print("\e[2mRunning: " + run + "\e[m\n"); + Terminal::get().async_process(run, run_path, [run](int exit_status) { + Terminal::get().print("\e[2m" + run + " returned: " + (exit_status == 0 ? "\e[32m" : "\e[31m") + std::to_string(exit_status) + "\e[m\n"); + }); + } + else { // Debug + Project::debugging = true; + Terminal::get().async_print("\e[2mDebugging: " + run + "\e[m\n"); + project->debug_start(run, run_path, command.debug_remote_host); + } + return true; + } + } + if(event->keyval == GDK_KEY_Escape) { EntryBox::get().hide(); } diff --git a/tests/filesystem_test.cpp b/tests/filesystem_test.cpp index 16e4059..ee9835e 100644 --- a/tests/filesystem_test.cpp +++ b/tests/filesystem_test.cpp @@ -105,6 +105,14 @@ int main() { g_assert(filesystem::get_relative_path("/test2/test.cc", "/test/base") == boost::filesystem::path("..") / ".." / "test2" / "test.cc"); } + { + g_assert(filesystem::get_absolute_path("./test1/test2", "/home") == boost::filesystem::path("/") / "home" / "." / "test1" / "test2"); + g_assert(filesystem::get_absolute_path("../test1/test2", "/home") == boost::filesystem::path("/") / "home" / ".." / "test1" / "test2"); + g_assert(filesystem::get_absolute_path("test1/test2", "/home") == boost::filesystem::path("/") / "home" / "test1" / "test2"); + g_assert(filesystem::get_absolute_path("/test1/test2", "/home") == boost::filesystem::path("/") / "test1" / "test2"); + g_assert(filesystem::get_absolute_path("~/test1/test2", "/home") == boost::filesystem::path("~") / "test1" / "test2"); + } + { boost::filesystem::path path = "/ro ot/te stæøå.txt"; auto uri = filesystem::get_uri_from_path(path); diff --git a/tests/stubs/project.cpp b/tests/stubs/project.cpp index 42079da..ac2183f 100644 --- a/tests/stubs/project.cpp +++ b/tests/stubs/project.cpp @@ -18,4 +18,6 @@ std::pair Project::Base::debug_get_run_arguments() { return std::make_pair("", ""); } -void Project::Base::debug_start() {} +void Project::Base::debug_compile_and_start() {} + +void Project::Base::debug_start(const std::string &command, const boost::filesystem::path &path, const std::string &remote_host) {}