diff --git a/.gitignore b/.gitignore index c30b100..ad5aad3 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,5 @@ !CMakeLists.txt !config.json !*.py + +build \ No newline at end of file diff --git a/README.md b/README.md index ef2e0f8..afc7120 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ towards libclang with speed and ease of use in mind. * Syntax highlighting for more than 100 different file types * C++ warnings and errors on the fly * C++ Fix-its +* Debug integration through lldb * Automated CMake processing * Fast C++ autocompletion * Keyword and buffer autocompletion for other file types @@ -44,6 +45,7 @@ See [enhancements](https://github.com/cppit/jucipp/labels/enhancement) for plann * gtksourceviewmm-3.0 * aspell * libclang +* lldb * [libclangmm](http://github.com/cppit/libclangmm/) (downloaded directly with git --recursive, no need to install) * [tiny-process-library](http://github.com/eidheim/tiny-process-library/) (downloaded directly with git --recursive, no need to install) diff --git a/docs/install.md b/docs/install.md index 672e5f3..86587c4 100644 --- a/docs/install.md +++ b/docs/install.md @@ -1,19 +1,22 @@ # juCi++ Installation Guide -- Linux - - [Debian/Ubuntu 15](#debianubuntu-15) - - [Ubuntu 14/Linux Mint 17](#ubuntu-14linux-mint-17) - - [Arch Linux](#arch-linux) -- OS X - - [Homebrew](#os-x-with-homebrew-httpbrewsh) -- Windows - - [MSYS 2](#windows-with-msys2-httpsmsys2githubio) - -## Debian/Ubuntu 15 +- Installation + - Linux + - [Debian testing/Linux Mint/Ubuntu](#debian-testinglinux-mintubuntu) + - [Debian stable](#debian-stable) + - [Arch Linux](#arch-linux) + - OS X + - [Homebrew](#os-x-with-homebrew-httpbrewsh) + - Windows + - [MSYS 2](#windows-with-msys2-httpsmsys2githubio) +- [Run](#run) + +## Debian testing/Linux Mint/Ubuntu +**Currently, if using another libclang version, the same version of lldb is needed.** + Install dependencies: ```sh -sudo apt-get install git cmake make g++ libclang-dev pkg-config libboost-system-dev libboost-thread-dev libboost-filesystem-dev libboost-log-dev libboost-regex-dev libgtksourceviewmm-3.0-dev aspell-en libaspell-dev -sudo apt-get install clang-format-3.6 || sudo apt-get install clang-format-3.5 +sudo apt-get install git cmake make g++ libclang-3.6-dev liblldb-3.6-dev clang-format-3.6 pkg-config libboost-system-dev libboost-thread-dev libboost-filesystem-dev libboost-log-dev libboost-regex-dev libgtksourceviewmm-3.0-dev aspell-en libaspell-dev ``` Get juCi++ source, compile and install: @@ -26,10 +29,12 @@ make sudo make install ``` -## Ubuntu 14/Linux Mint 17 +## Debian stable +**Currently, if using another libclang version, the same version of lldb is needed.** + Install dependencies: ```sh -sudo apt-get install git cmake make g++ libclang-3.6-dev clang-format-3.6 pkg-config libboost-system-dev libboost-thread-dev libboost-filesystem-dev libboost-log-dev libboost-regex-dev libgtksourceviewmm-3.0-dev aspell-en libaspell-dev +sudo apt-get install git cmake make g++ libclang-3.5-dev liblldb-3.5-dev clang-format-3.5 pkg-config libboost-system-dev libboost-thread-dev libboost-filesystem-dev libboost-log-dev libboost-regex-dev libgtksourceviewmm-3.0-dev aspell-en libaspell-dev ``` Get juCi++ source, compile and install: @@ -50,8 +55,7 @@ Alternatively, follow the instructions below. Install dependencies: ```sh -#as root -pacman -S git cmake make clang gtksourceviewmm boost aspell aspell-en +sudo pacman -S git cmake make clang lldb gtksourceviewmm boost aspell aspell-en ``` Get juCi++ source, compile and install: @@ -61,14 +65,16 @@ mkdir jucipp/build cd jucipp/build cmake .. make -# as root -make install +sudo make install ``` ## OS X with Homebrew (http://brew.sh/) -Install dependencies (installing llvm may take some time): +**Installing llvm may take some time, and you need to follow the lldb code signing instructions.** + +Install dependencies: ```sh -brew install cmake --with-clang llvm pkg-config boost homebrew/x11/gtksourceviewmm3 aspell clang-format +brew install --with-clang --with-lldb llvm +brew install cmake pkg-config boost homebrew/x11/gtksourceviewmm3 aspell clang-format ``` Get juCi++ source, compile and install: @@ -82,6 +88,8 @@ make install ``` ##Windows with MSYS2 (https://msys2.github.io/) +**MSYS2 does not yet support lldb, but you can still compile juCi++ without debug support.** + Install dependencies (replace `x86_64` with `i686` for 32-bit MSYS2 installs): ```sh pacman -S git mingw-w64-x86_64-cmake make mingw-w64-x86_64-toolchain mingw-w64-x86_64-clang mingw-w64-x86_64-gtkmm3 mingw-w64-x86_64-gtksourceviewmm3 mingw-w64-x86_64-boost mingw-w64-x86_64-aspell mingw-w64-x86_64-aspell-en @@ -97,34 +105,11 @@ make make install ``` - - ## Run ```sh juci ``` - - diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 86bb96a..1c08d05 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -28,7 +28,19 @@ endif() INCLUDE(FindPkgConfig) find_package(LibClang REQUIRED) - +string(REPLACE libclang liblldb LIBLLDB_LIBRARIES "${LIBCLANG_LIBRARIES}") +if(EXISTS "${LIBLLDB_LIBRARIES}") + set(LIBLLDB_FOUND TRUE) +elseif(EXISTS "${LIBLLDB_LIBRARIES}.1") + set(LIBLLDB_LIBRARIES "${LIBLLDB_LIBRARIES}.1") + set(LIBLLDB_FOUND TRUE) +endif() +if(LIBLLDB_FOUND) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DJUCI_ENABLE_DEBUG") +else() + set(LIBLLDB_LIBRARIES "") + message("liblldb not found. Compiling juCi++ without debugging support") +endif() #find_package(PythonLibs 2.7) #find_package(Boost 1.55 COMPONENTS python thread log system filesystem REQUIRED) @@ -90,6 +102,10 @@ set(source_files juci.h ../tiny-process-library/process.cpp) +if(LIBLLDB_FOUND) + list(APPEND source_files debug.h debug.cc) +endif() + if(MSYS) list(APPEND source_files dialogs_unix.cc) #dialogs_win.cc does not work any more because of missing SHCreateItemFromParsingName list(APPEND source_files ../tiny-process-library/process_win.cpp) @@ -136,6 +152,7 @@ target_link_libraries(${project_name} ${GTKSVMM_LIBRARIES} ${Boost_LIBRARIES} ${ASPELL_LIBRARIES} + ${LIBLLDB_LIBRARIES} #${PYTHON_LIBRARIES} ) diff --git a/src/cmake.cc b/src/cmake.cc index 0d8ab02..f5d1260 100644 --- a/src/cmake.cc +++ b/src/cmake.cc @@ -5,6 +5,8 @@ #include "terminal.h" #include +std::unordered_set CMake::debug_build_needed; + CMake::CMake(const boost::filesystem::path &path) { const auto find_cmake_project=[this](const boost::filesystem::path &cmake_path) { for(auto &line: filesystem::read_lines(cmake_path)) { @@ -31,21 +33,15 @@ CMake::CMake(const boost::filesystem::path &path) { break; search_path=search_path.parent_path(); } - - if(!project_path.empty()) { - auto default_build_path=get_default_build_path(project_path); - if(!default_build_path.empty() && !boost::filesystem::exists(default_build_path/"compile_commands.json")) - create_compile_commands(project_path); - } } -boost::filesystem::path CMake::get_default_build_path(const boost::filesystem::path &path) { +boost::filesystem::path CMake::get_default_build_path(const boost::filesystem::path &project_path) { boost::filesystem::path default_build_path=Config::get().terminal.default_build_path; const std::string path_variable_project_directory_name=""; size_t pos=0; auto default_build_path_string=default_build_path.string(); - auto path_filename_string=path.filename().string(); + auto path_filename_string=project_path.filename().string(); while((pos=default_build_path_string.find(path_variable_project_directory_name, pos))!=std::string::npos) { default_build_path_string.replace(pos, path_variable_project_directory_name.size(), path_filename_string); pos+=path_filename_string.size(); @@ -54,28 +50,70 @@ boost::filesystem::path CMake::get_default_build_path(const boost::filesystem::p default_build_path=default_build_path_string; if(default_build_path.is_relative()) - default_build_path=path/default_build_path; - + default_build_path=project_path/default_build_path; + + return default_build_path; +} + +boost::filesystem::path CMake::get_debug_build_path(const boost::filesystem::path &project_path) { + boost::filesystem::path debug_build_path=Config::get().terminal.debug_build_path; + + const std::string path_variable_project_directory_name=""; + size_t pos=0; + auto debug_build_path_string=debug_build_path.string(); + auto path_filename_string=project_path.filename().string(); + while((pos=debug_build_path_string.find(path_variable_project_directory_name, pos))!=std::string::npos) { + debug_build_path_string.replace(pos, path_variable_project_directory_name.size(), path_filename_string); + pos+=path_filename_string.size(); + } + if(pos!=0) + debug_build_path=debug_build_path_string; + + const std::string path_variable_default_build_path=""; + pos=0; + debug_build_path_string=debug_build_path.string(); + auto default_build_path=Config::get().terminal.default_build_path; + while((pos=debug_build_path_string.find(path_variable_default_build_path, pos))!=std::string::npos) { + debug_build_path_string.replace(pos, path_variable_default_build_path.size(), default_build_path); + pos+=default_build_path.size(); + } + if(pos!=0) + debug_build_path=debug_build_path_string; + + if(debug_build_path.is_relative()) + debug_build_path=project_path/debug_build_path; + + return debug_build_path; +} + +bool CMake::create_default_build(const boost::filesystem::path &project_path, bool force) { + if(project_path.empty()) + return false; + + if(!boost::filesystem::exists(project_path/"CMakeLists.txt")) + return false; + + auto default_build_path=get_default_build_path(project_path); + if(default_build_path.empty()) + return false; if(!boost::filesystem::exists(default_build_path)) { boost::system::error_code ec; boost::filesystem::create_directories(default_build_path, ec); if(ec) { Terminal::get().print("Error: could not create "+default_build_path.string()+": "+ec.message()+"\n", true); - return boost::filesystem::path(); + return false; } } - return default_build_path; -} - -bool CMake::create_compile_commands(const boost::filesystem::path &path) { - auto default_build_path=get_default_build_path(path); - if(default_build_path.empty()) - return false; + if(!force && boost::filesystem::exists(default_build_path/"compile_commands.json")) + return true; + + debug_build_needed.emplace(project_path.string()); + auto compile_commands_path=default_build_path/"compile_commands.json"; - Dialog::Message message("Creating "+compile_commands_path.string()); + Dialog::Message message("Creating/updating default build"); auto exit_status=Terminal::get().process(Config::get().terminal.cmake_command+" "+ - path.string()+" -DCMAKE_EXPORT_COMPILE_COMMANDS=ON", default_build_path); + filesystem::escape_argument(project_path)+" -DCMAKE_EXPORT_COMPILE_COMMANDS=ON", default_build_path); message.hide(); if(exit_status==EXIT_SUCCESS) { #ifdef _WIN32 //Temporary fix to MSYS2's libclang @@ -97,6 +135,71 @@ bool CMake::create_compile_commands(const boost::filesystem::path &path) { return false; } +bool CMake::create_debug_build(const boost::filesystem::path &project_path) { + if(project_path.empty()) + return false; + + if(!boost::filesystem::exists(project_path/"CMakeLists.txt")) + return false; + + auto debug_build_path=get_debug_build_path(project_path); + if(debug_build_path.empty()) + return false; + if(!boost::filesystem::exists(debug_build_path)) { + boost::system::error_code ec; + boost::filesystem::create_directories(debug_build_path, ec); + if(ec) { + Terminal::get().print("Error: could not create "+debug_build_path.string()+": "+ec.message()+"\n", true); + return false; + } + } + + if(boost::filesystem::exists(debug_build_path/"CMakeCache.txt")) { + auto it=debug_build_needed.find(project_path.string()); + if(it==debug_build_needed.end()) + return true; + } + + std::unique_ptr message; + message=std::unique_ptr(new Dialog::Message("Creating/updating debug build")); + auto exit_status=Terminal::get().process(Config::get().terminal.cmake_command+" "+ + filesystem::escape_argument(project_path)+" -DCMAKE_BUILD_TYPE=Debug", debug_build_path); + if(message) + message->hide(); + if(exit_status==EXIT_SUCCESS) { + auto it=debug_build_needed.find(project_path.string()); + if(it!=debug_build_needed.end()) + debug_build_needed.erase(it); + return true; + } + return false; +} + +boost::filesystem::path CMake::get_executable(const boost::filesystem::path &file_path) { + auto executables = get_functions_parameters("add_executable"); + + //Attempt to find executable based add_executable files and opened tab + boost::filesystem::path executable_path; + if(!file_path.empty()) { + for(auto &executable: executables) { + if(executable.second.size()>1) { + for(size_t c=1;c0 && executables[0].second.size()>0) + executable_path=executables[0].first.parent_path()/executables[0].second[0]; + + return executable_path; +} + void CMake::read_files() { for(auto &path: paths) files.emplace_back(filesystem::read(path)); diff --git a/src/cmake.h b/src/cmake.h index e995b23..bfa463b 100644 --- a/src/cmake.h +++ b/src/cmake.h @@ -3,20 +3,25 @@ #include #include #include +#include class CMake { public: CMake(const boost::filesystem::path &path); - std::vector > > get_functions_parameters(const std::string &name); - static boost::filesystem::path get_default_build_path(const boost::filesystem::path &path); - static bool create_compile_commands(const boost::filesystem::path &path); - + boost::filesystem::path project_path; std::vector paths; + + static boost::filesystem::path get_default_build_path(const boost::filesystem::path &project_path); + static boost::filesystem::path get_debug_build_path(const boost::filesystem::path &project_path); + static bool create_default_build(const boost::filesystem::path &project_path, bool force=false); + static bool create_debug_build(const boost::filesystem::path &project_path); + + boost::filesystem::path get_executable(const boost::filesystem::path &file_path); + + std::vector > > get_functions_parameters(const std::string &name); +private: std::vector files; - boost::filesystem::path project_path; std::unordered_map variables; -private: - void read_files(); void remove_tabs(); void remove_comments(); @@ -26,5 +31,6 @@ private: void parse(); std::vector get_function_parameters(std::string &data); bool parsed=false; + static std::unordered_set debug_build_needed; }; #endif //JUCI_CMAKE_H_ diff --git a/src/config.cc b/src/config.cc index 732344f..b04ec8a 100644 --- a/src/config.cc +++ b/src/config.cc @@ -86,6 +86,7 @@ void Config::retrieve_config() { window.default_size = {cfg.get("default_window_size.width"), cfg.get("default_window_size.height")}; terminal.default_build_path=cfg.get("project.default_build_path"); + terminal.debug_build_path=cfg.get("project.debug_build_path"); terminal.make_command=cfg.get("project.make_command"); terminal.cmake_command=cfg.get("project.cmake_command"); terminal.history_size=cfg.get("terminal_history_size"); diff --git a/src/config.h b/src/config.h index 6599c3a..8ebba6f 100644 --- a/src/config.h +++ b/src/config.h @@ -25,6 +25,7 @@ public: class Terminal { public: std::string default_build_path; + std::string debug_build_path; std::string cmake_command; std::string make_command; std::string clang_format_command; diff --git a/src/debug.cc b/src/debug.cc new file mode 100644 index 0000000..2e72c52 --- /dev/null +++ b/src/debug.cc @@ -0,0 +1,512 @@ +#include "debug.h" +#include +#ifdef __APPLE__ +#include +#endif +#include +#include +#include "terminal.h" +#include "filesystem.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; //TODO: remove + +extern char **environ; + +void log(const char *msg, void *) { + std::cout << "debugger log: " << msg << std::endl; +} + +Debug::Debug(): state(lldb::StateType::eStateInvalid), buffer_size(131072) { +#ifdef __APPLE__ + auto debugserver_path=boost::filesystem::path("/usr/local/opt/llvm/bin/debugserver"); + if(boost::filesystem::exists(debugserver_path)) + setenv("LLDB_DEBUGSERVER_PATH", debugserver_path.string().c_str(), 0); +#endif +} + +void Debug::start(const std::string &command, const boost::filesystem::path &path, + std::shared_ptr > > breakpoints, + std::function callback, + std::function status_callback, + std::function stop_callback) { + if(!debugger) { + lldb::SBDebugger::Initialize(); + debugger=std::unique_ptr(new lldb::SBDebugger(lldb::SBDebugger::Create(true, log, nullptr))); + listener=std::unique_ptr(new lldb::SBListener("juCi++ lldb listener")); + } + + //Create executable string and argument array + std::string executable; + std::vector arguments; + size_t start_pos=std::string::npos; + bool quote=false; + bool double_quote=false; + bool symbol=false; + for(size_t c=0;c<=command.size();c++) { + if(c==command.size() || (!quote && !double_quote && !symbol && command[c]==' ')) { + if(c>0 && start_pos!=std::string::npos) { + if(executable.empty()) + executable=filesystem::unescape(command.substr(start_pos, c-start_pos)); + else + arguments.emplace_back(filesystem::unescape(command.substr(start_pos, c-start_pos))); + start_pos=std::string::npos; + } + } + else if(command[c]=='\\' && !quote && !double_quote) + symbol=true; + else if(symbol) + symbol=false; + else if(command[c]=='\'' && !double_quote) + quote=!quote; + else if(command[c]=='"' && !quote) + double_quote=!double_quote; + if(cCreateTarget(executable.c_str()); + if(!target.IsValid()) { + Terminal::get().async_print("Error (debug): Could not create debug target to: "+executable+'\n', true); + if(callback) + callback(-1); + return; + } + + //Set breakpoints + if(breakpoints) { + for(auto &breakpoint: *breakpoints) { + if(!(target.BreakpointCreateByLocation(breakpoint.first.string().c_str(), breakpoint.second)).IsValid()) { + Terminal::get().async_print("Error (debug): Could not create breakpoint at: "+breakpoint.first.string()+":"+std::to_string(breakpoint.second)+'\n', true); + if(callback) + callback(-1); + return; + } + } + } + + lldb::SBError error; + process = std::unique_ptr(new lldb::SBProcess(target.Launch(*listener, argv, (const char**)environ, nullptr, nullptr, nullptr, path.string().c_str(), lldb::eLaunchFlagNone, false, error))); + if(error.Fail()) { + Terminal::get().async_print(std::string("Error (debug): ")+error.GetCString()+'\n', true); + if(callback) + callback(-1); + return; + } + if(debug_thread.joinable()) + debug_thread.join(); + debug_thread=std::thread([this, callback, status_callback, stop_callback]() { + lldb::SBEvent event; + while(true) { + event_mutex.lock(); + if(listener->GetNextEvent(event)) { + if((event.GetType() & lldb::SBProcess::eBroadcastBitStateChanged)>0) { + auto state=process->GetStateFromEvent(event); + this->state=state; + + if(state==lldb::StateType::eStateStopped) { + for(uint32_t c=0;cGetNumThreads();c++) { + auto thread=process->GetThreadAtIndex(c); + if(thread.GetStopReason()>=2) { + process->SetSelectedThreadByIndexID(thread.GetIndexID()); + break; + } + } + } + + //Update debug status + lldb::SBStream stream; + event.GetDescription(stream); + std::string event_desc=stream.GetData(); + event_desc.pop_back(); + auto pos=event_desc.rfind(" = "); + if(status_callback && pos!=std::string::npos) { + auto status=event_desc.substr(pos+3); + if(state==lldb::StateType::eStateStopped) { + char buffer[100]; + auto thread=process->GetSelectedThread(); + auto n=thread.GetStopDescription(buffer, 100); + if(n>0) + status+=" ("+std::string(buffer, n<=100?n:100)+")"; + auto line_entry=thread.GetSelectedFrame().GetLineEntry(); + if(line_entry.IsValid()) { + lldb::SBStream stream; + line_entry.GetFileSpec().GetDescription(stream); + status +=" - "+boost::filesystem::path(stream.GetData()).filename().string()+":"+std::to_string(line_entry.GetLine()); + } + } + status_callback(status); + } + + if(state==lldb::StateType::eStateStopped) { + if(stop_callback) { + auto line_entry=process->GetSelectedThread().GetSelectedFrame().GetLineEntry(); + if(line_entry.IsValid()) { + lldb::SBStream stream; + line_entry.GetFileSpec().GetDescription(stream); + auto column=line_entry.GetColumn(); + if(column==0) + column=1; + stop_callback(stream.GetData(), line_entry.GetLine(), column); + } + else + stop_callback("", 0, 0); + } + } + else if(state==lldb::StateType::eStateRunning) { + stop_callback("", 0, 0); + } + else if(state==lldb::StateType::eStateExited) { + auto exit_status=process->GetExitStatus(); + if(callback) + callback(exit_status); + if(status_callback) + status_callback(""); + if(stop_callback) + stop_callback("", 0, 0); + process.reset(); + this->state=lldb::StateType::eStateInvalid; + event_mutex.unlock(); + return; + } + else if(state==lldb::StateType::eStateCrashed) { + if(callback) + callback(-1); + if(status_callback) + status_callback(""); + if(stop_callback) + stop_callback("", 0, 0); + process.reset(); + this->state=lldb::StateType::eStateInvalid; + event_mutex.unlock(); + return; + } + } + if((event.GetType() & lldb::SBProcess::eBroadcastBitSTDOUT)>0) { + char buffer[buffer_size]; + size_t n; + while((n=process->GetSTDOUT(buffer, buffer_size))!=0) + Terminal::get().async_print(std::string(buffer, n)); + } + //TODO: for some reason stderr is redirected to stdout + if((event.GetType() & lldb::SBProcess::eBroadcastBitSTDERR)>0) { + char buffer[buffer_size]; + size_t n; + while((n=process->GetSTDERR(buffer, buffer_size))!=0) + Terminal::get().async_print(std::string(buffer, n), true); + } + } + event_mutex.unlock(); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + } + }); +} + +void Debug::continue_debug() { + event_mutex.lock(); + if(state==lldb::StateType::eStateStopped) + process->Continue(); + event_mutex.unlock(); +} + +void Debug::stop() { + event_mutex.lock(); + if(state==lldb::StateType::eStateRunning) { + auto error=process->Stop(); + if(error.Fail()) + Terminal::get().async_print(std::string("Error (debug): ")+error.GetCString()+'\n', true); + } + event_mutex.unlock(); +} + +void Debug::kill() { + event_mutex.lock(); + if(process) { + auto error=process->Kill(); + if(error.Fail()) + Terminal::get().async_print(std::string("Error (debug): ")+error.GetCString()+'\n', true); + } + event_mutex.unlock(); +} + +void Debug::step_over() { + event_mutex.lock(); + if(state==lldb::StateType::eStateStopped) { + process->GetSelectedThread().StepOver(); + } + event_mutex.unlock(); +} + +void Debug::step_into() { + event_mutex.lock(); + if(state==lldb::StateType::eStateStopped) { + process->GetSelectedThread().StepInto(); + } + event_mutex.unlock(); +} + +void Debug::step_out() { + event_mutex.lock(); + if(state==lldb::StateType::eStateStopped) { + process->GetSelectedThread().StepOut(); + } + event_mutex.unlock(); +} + +std::pair Debug::run_command(const std::string &command) { + std::pair command_return; + event_mutex.lock(); + if(state==lldb::StateType::eStateStopped || state==lldb::StateType::eStateRunning) { + lldb::SBCommandReturnObject command_return_object; + debugger->GetCommandInterpreter().HandleCommand(command.c_str(), command_return_object, true); + command_return.first=command_return_object.GetOutput(); + command_return.second=command_return_object.GetError(); + } + event_mutex.unlock(); + return command_return; +} + +std::vector Debug::get_backtrace() { + std::vector backtrace; + event_mutex.lock(); + if(state==lldb::StateType::eStateStopped) { + auto thread=process->GetSelectedThread(); + for(uint32_t c_f=0;c_f Debug::get_variables() { + std::vector variables; + event_mutex.lock(); + if(state==lldb::StateType::eStateStopped) { + for(uint32_t c_t=0;c_tGetNumThreads();c_t++) { + auto thread=process->GetThreadAtIndex(c_t); + for(uint32_t c_f=0;c_fSetSelectedThreadByIndexID(thread_index_id); + process->GetSelectedThread().SetSelectedFrame(frame_index);; + } + event_mutex.unlock(); +} + +void Debug::delete_debug() { + kill(); + if(debug_thread.joinable()) + debug_thread.join(); +} + +std::string Debug::get_value(const std::string &variable, const boost::filesystem::path &file_path, unsigned int line_nr, unsigned int line_index) { + std::string variable_value; + event_mutex.lock(); + if(state==lldb::StateType::eStateStopped) { + auto frame=process->GetSelectedThread().GetSelectedFrame(); + + auto values=frame.GetVariables(true, true, true, false); + //First try to find variable based on name, file and line number + for(uint32_t value_index=0;value_indexGetSelectedThread(); + auto thread_return_value=thread.GetStopReturnValue(); + if(thread_return_value.IsValid()) { + auto line_entry=thread.GetSelectedFrame().GetLineEntry(); + if(line_entry.IsValid()) { + lldb::SBStream stream; + line_entry.GetFileSpec().GetDescription(stream); + if(boost::filesystem::path(stream.GetData())==file_path && line_entry.GetLine()==line_nr && + (line_entry.GetColumn()==0 || line_entry.GetColumn()==line_index)) { + lldb::SBStream stream; + thread_return_value.GetDescription(stream); + return_value=stream.GetData(); + } + } + } + } + event_mutex.unlock(); + return return_value; +} + +bool Debug::is_invalid() { + bool invalid; + event_mutex.lock(); + invalid=state==lldb::StateType::eStateInvalid; + event_mutex.unlock(); + return invalid; +} + +bool Debug::is_stopped() { + bool stopped; + event_mutex.lock(); + stopped=state==lldb::StateType::eStateStopped; + event_mutex.unlock(); + return stopped; +} + +bool Debug::is_running() { + bool running; + event_mutex.lock(); + running=state==lldb::StateType::eStateRunning; + event_mutex.unlock(); + return running; +} + +void Debug::add_breakpoint(const boost::filesystem::path &file_path, int line_nr) { + event_mutex.lock(); + if(state==lldb::eStateStopped || state==lldb::eStateRunning) { + if(!(process->GetTarget().BreakpointCreateByLocation(file_path.string().c_str(), line_nr)).IsValid()) + Terminal::get().async_print("Error (debug): Could not create breakpoint at: "+file_path.string()+":"+std::to_string(line_nr)+'\n', true); + } + event_mutex.unlock(); +} + +void Debug::remove_breakpoint(const boost::filesystem::path &file_path, int line_nr, int line_count) { + event_mutex.lock(); + if(state==lldb::eStateStopped || state==lldb::eStateRunning) { + auto target=process->GetTarget(); + for(int line_nr_try=line_nr;line_nr_try(line_nr_try)) { + auto file_spec=line_entry.GetFileSpec(); + boost::filesystem::path breakpoint_path=file_spec.GetDirectory(); + breakpoint_path/=file_spec.GetFilename(); + if(breakpoint_path==file_path) { + if(!target.BreakpointDelete(breakpoint.GetID())) + Terminal::get().async_print("Error (debug): Could not delete breakpoint at: "+file_path.string()+":"+std::to_string(line_nr)+'\n', true); + event_mutex.unlock(); + return; + } + } + } + } + } + } + event_mutex.unlock(); +} + +void Debug::write(const std::string &buffer) { + event_mutex.lock(); + if(state==lldb::StateType::eStateRunning) { + process->PutSTDIN(buffer.c_str(), buffer.size()); + } + event_mutex.unlock(); +} diff --git a/src/debug.h b/src/debug.h new file mode 100644 index 0000000..07fd4d2 --- /dev/null +++ b/src/debug.h @@ -0,0 +1,83 @@ +#ifndef JUCI_DEBUG_H_ +#define JUCI_DEBUG_H_ + +#include +#include +#include +#include +#include +#include +#include + +class Debug { +public: + class Frame { + public: + uint32_t index; + std::string module_filename; + std::string file_path; + std::string function_name; + int line_nr; + int line_index; + }; + class Variable { + public: + uint32_t thread_index_id; + uint32_t frame_index; + std::string name; + std::string value; + boost::filesystem::path file_path; + int line_nr; + int line_index; + }; +private: + Debug(); +public: + static Debug &get() { + static Debug singleton; + return singleton; + } + + void start(const std::string &command, const boost::filesystem::path &path="", + std::shared_ptr > > breakpoints=nullptr, + std::function callback=nullptr, + std::function status_callback=nullptr, + std::function stop_callback=nullptr); + void continue_debug(); //can't use continue as function name + void stop(); + void kill(); + void step_over(); + void step_into(); + void step_out(); + std::pair run_command(const std::string &command); + std::vector get_backtrace(); + std::vector get_variables(); + void select_frame(uint32_t frame_index, uint32_t thread_index_id=0); + + void delete_debug(); //can't use delete as function name + + std::string get_value(const std::string &variable, const boost::filesystem::path &file_path, unsigned int line_nr, unsigned int line_index); + std::string get_return_value(const boost::filesystem::path &file_path, unsigned int line_nr, unsigned int line_index); + + bool is_invalid(); + bool is_stopped(); + bool is_running(); + + void add_breakpoint(const boost::filesystem::path &file_path, int line_nr); + void remove_breakpoint(const boost::filesystem::path &file_path, int line_nr, int line_count); + + void write(const std::string &buffer); + +private: + std::unique_ptr debugger; + std::unique_ptr listener; + std::unique_ptr process; + std::thread debug_thread; + + lldb::StateType state; + std::mutex event_mutex; + + size_t buffer_size; +}; + +#endif diff --git a/src/directories.cc b/src/directories.cc index ab027a9..0265cf5 100644 --- a/src/directories.cc +++ b/src/directories.cc @@ -130,6 +130,7 @@ void Directories::open(const boost::filesystem::path& dir_path) { update_mutex.unlock(); cmake=std::unique_ptr(new CMake(dir_path)); + CMake::create_default_build(cmake->project_path); auto project=cmake->get_functions_parameters("project"); if(project.size()>0 && project[0].second.size()>0) get_column(0)->set_title(project[0].second[0]); diff --git a/src/files.h b/src/files.h index 1400e0f..0970ccf 100644 --- a/src/files.h +++ b/src/files.h @@ -2,7 +2,7 @@ #define JUCI_FILES_H_ #include -#define JUCI_VERSION "1.0.1" +#define JUCI_VERSION "1.1.0-4" const std::string configjson = "{\n" @@ -92,11 +92,25 @@ const std::string configjson = " \"source_rename\": \"r\",\n" " \"source_goto_next_diagnostic\": \"e\",\n" " \"source_apply_fix_its\": \"space\",\n" +" \"project_set_run_arguments\": \"\",\n" " \"compile_and_run\": \"Return\",\n" " \"compile\": \"Return\",\n" +" \"compile_and_run\": \"Return\",\n" " \"run_command\": \"Return\",\n" " \"kill_last_running\": \"Escape\",\n" " \"force_kill_last_running\": \"Escape\",\n" +" \"debug_set_run_arguments\": \"\",\n" +" \"debug_start_continue\": \"y\",\n" +" \"debug_stop\": \"y\",\n" +" \"debug_kill\": \"k\",\n" +" \"debug_step_over\": \"j\",\n" +" \"debug_step_into\": \"t\",\n" +" \"debug_step_out\": \"t\",\n" +" \"debug_backtrace\": \"j\",\n" +" \"debug_show_variables\": \"b\",\n" +" \"debug_run_command\": \"Return\",\n" +" \"debug_toggle_breakpoint\": \"b\",\n" +" \"debug_goto_stop\": \"l\",\n" #ifdef __linux " \"next_tab\": \"Tab\",\n" " \"previous_tab\": \"Tab\",\n" @@ -109,6 +123,8 @@ const std::string configjson = " \"project\": {\n" " \"default_build_path_comment\": \"Use to insert the project top level directory name\",\n" " \"default_build_path\": \"./build\",\n" +" \"debug_build_path_comment\": \"Use to insert the project top level directory name, and to insert your default_build_path setting.\",\n" +" \"debug_build_path\": \"/debug\",\n" #ifdef _WIN32 " \"cmake_command\": \"cmake -G\\\"MSYS Makefiles\\\" -DCMAKE_INSTALL_PREFIX="+JUCI_CMAKE_INSTALL_PREFIX+"\",\n" #else diff --git a/src/filesystem.cc b/src/filesystem.cc index 02d05fd..7c46dea 100644 --- a/src/filesystem.cc +++ b/src/filesystem.cc @@ -135,3 +135,29 @@ bool filesystem::write(const std::string &path, Glib::RefPtr bu } return false; } + +std::string filesystem::escape_argument(const std::string &argument) { + auto escaped=argument; + size_t pos=0; + while((pos=escaped.find(' ', pos))!=std::string::npos) { + escaped.replace(pos, 1, "\\ "); + pos+=2; + } + return escaped; +} + +std::string filesystem::unescape(const std::string &argument) { + auto escaped=argument; + size_t pos=0; + while((pos=escaped.find("\\ ", pos))!=std::string::npos) { + escaped.replace(pos, 2, " "); + pos+=1; + } + if(escaped.size()>=2) { + if((escaped[0]=='\'' && escaped[escaped.size()-1]=='\'') || + (escaped[0]=='"' && escaped[escaped.size()-1]=='"')) { + escaped=escaped.substr(1, escaped.size()-2); + } + } + return escaped; +} diff --git a/src/filesystem.h b/src/filesystem.h index 10d51bb..d2b3e7a 100644 --- a/src/filesystem.h +++ b/src/filesystem.h @@ -25,5 +25,8 @@ public: static bool write(const std::string &path, Glib::RefPtr text_buffer); static bool write(const boost::filesystem::path &path, Glib::RefPtr text_buffer) { return write(path.string(), text_buffer); } + static std::string escape_argument(const std::string &argument); + static std::string escape_argument(const boost::filesystem::path &argument) { return escape_argument(argument.string()); }; + static std::string unescape(const std::string &argument); }; #endif // JUCI_FILESYSTEM_H_ diff --git a/src/juci.cc b/src/juci.cc index 250afab..3c52331 100644 --- a/src/juci.cc +++ b/src/juci.cc @@ -32,7 +32,7 @@ int Application::on_command_line(const Glib::RefPtr files.emplace_back(new_p); } else - Terminal::get().print("Error: folder path "+parent_p.string()+" does not exist.\n", true); + errors.emplace_back("Error: folder path "+parent_p.string()+" does not exist.\n"); } } } @@ -66,8 +66,12 @@ void Application::on_activate() { another_juci_app.detach(); } } + for(auto &file: files) Window::get().notebook.open(file); + + for(auto &error: errors) + Terminal::get().print(error, true); } void Application::on_startup() { diff --git a/src/juci.h b/src/juci.h index a957b87..81682f2 100644 --- a/src/juci.h +++ b/src/juci.h @@ -13,6 +13,7 @@ public: private: std::vector directories; std::vector files; + std::vector errors; }; #endif // JUCI_JUCI_H_ diff --git a/src/menu.cc b/src/menu.cc index 36e23a7..3627947 100644 --- a/src/menu.cc +++ b/src/menu.cc @@ -239,6 +239,11 @@ Menu::Menu() { " _Project" "
" " " + " _Set _Run _Arguments" + " app.project_set_run_arguments" + +accels["project_set_run_arguments"]+ //For Ubuntu... + " " + " " " _Compile _and _Run" " app.compile_and_run" +accels["compile_and_run"]+ //For Ubuntu... @@ -269,6 +274,82 @@ Menu::Menu() { " " "" " " + " _Debug" + "
" + " " + " _Set _Run _Arguments" + " app.debug_set_run_arguments" + +accels["debug_set_run_arguments"]+ //For Ubuntu... + " " + " " + " _Start/_Continue" + " app.debug_start_continue" + +accels["debug_start_continue"]+ //For Ubuntu... + " " + " " + " _Stop" + " app.debug_stop" + +accels["debug_stop"]+ //For Ubuntu... + " " + " " + " _Kill" + " app.debug_kill" + +accels["debug_kill"]+ //For Ubuntu... + " " + "
" + "
" + " " + " _Step _Over" + " app.debug_step_over" + +accels["debug_step_over"]+ //For Ubuntu... + " " + " " + " _Step _Into" + " app.debug_step_into" + +accels["debug_step_into"]+ //For Ubuntu... + " " + " " + " _Step _Out" + " app.debug_step_out" + +accels["debug_step_out"]+ //For Ubuntu... + " " + "
" + "
" + " " + " _Backtrace" + " app.debug_backtrace" + +accels["debug_backtrace"]+ //For Ubuntu... + " " + " " + " _Show _Variables" + " app.debug_show_variables" + +accels["debug_show_variables"]+ //For Ubuntu... + " " + "
" + "
" + " " + " _Run Command" + " app.debug_run_command" + +accels["debug_run_command"]+ //For Ubuntu... + " " + "
" + "
" + " " + " _Toggle _Breakpoint" + " app.debug_toggle_breakpoint" + +accels["debug_toggle_breakpoint"]+ //For Ubuntu... + " " + "
" + "
" + " " + " _Go _to _Stop" + " app.debug_goto_stop" + +accels["debug_goto_stop"]+ //For Ubuntu... + " " + "
" + "
" + "" + " " " _Window" "
" " " diff --git a/src/notebook.cc b/src/notebook.cc index f42f022..a0aa6f3 100644 --- a/src/notebook.cc +++ b/src/notebook.cc @@ -110,8 +110,7 @@ void Notebook::open(const boost::filesystem::path &file_path) { } } if(language && (language->get_id()=="chdr" || language->get_id()=="cpphdr" || language->get_id()=="c" || language->get_id()=="cpp" || language->get_id()=="objc")) { - if(boost::filesystem::exists(project_path.string()+"/CMakeLists.txt") && !boost::filesystem::exists(CMake::get_default_build_path(project_path)/"compile_commands.json")) - CMake::create_compile_commands(project_path); + CMake::create_default_build(project_path); source_views.emplace_back(new Source::ClangView(file_path, project_path, language)); } else @@ -249,15 +248,18 @@ bool Notebook::save(int page) { if(view->file_path.filename()=="CMakeLists.txt") { auto &directories=Directories::get(); if(directories.cmake && directories.cmake->project_path!="" && view->file_path.generic_string().substr(0, directories.cmake->project_path.generic_string().size()+1)==directories.cmake->project_path.generic_string()+'/') { - if(CMake::create_compile_commands(directories.cmake->project_path)) + if(CMake::create_default_build(directories.cmake->project_path, true)) project_path=directories.cmake->project_path; } else { CMake cmake(view->file_path.parent_path()); - if(cmake.project_path!="" && CMake::create_compile_commands(cmake.project_path)) + if(CMake::create_default_build(cmake.project_path, true)) project_path=cmake.project_path; } if(project_path!="") { + auto debug_project_path=CMake::get_debug_build_path(project_path); + if(!debug_project_path.empty() && boost::filesystem::exists(debug_project_path)) + CMake::create_debug_build(project_path); for(auto source_view: source_views) { if(auto source_clang_view=dynamic_cast(source_view)) { if(project_path==source_clang_view->project_path) @@ -307,6 +309,7 @@ bool Notebook::close(int page) { #if GTKSOURCEVIEWMM_MAJOR_VERSION > 2 & GTKSOURCEVIEWMM_MINOR_VERSION > 17 source_maps.erase(source_maps.begin()+index); #endif + auto source_view=source_views.at(index); if(auto source_clang_view=dynamic_cast(source_view)) source_clang_view->async_delete(); diff --git a/src/selectiondialog.cc b/src/selectiondialog.cc index 187a304..8ef0754 100644 --- a/src/selectiondialog.cc +++ b/src/selectiondialog.cc @@ -60,15 +60,13 @@ list_view_text(use_markup), start_mark(start_mark), show_search_entry(show_searc }); list_view_text.signal_event_after().connect([this](GdkEvent* event){ - if(event->type==GDK_KEY_PRESS || event->type==GDK_BUTTON_PRESS) { - update_tooltips(); - } + if(event->type==GDK_KEY_PRESS || event->type==GDK_BUTTON_PRESS) + cursor_changed(); }); if(show_search_entry) { search_entry.signal_event_after().connect([this](GdkEvent* event){ - if(event->type==GDK_KEY_PRESS || event->type==GDK_BUTTON_PRESS) { - update_tooltips(); - } + if(event->type==GDK_KEY_PRESS || event->type==GDK_BUTTON_PRESS) + cursor_changed(); }); } @@ -83,64 +81,47 @@ list_view_text(use_markup), start_mark(start_mark), show_search_entry(show_searc } } +void SelectionDialogBase::cursor_changed() { + if(!shown) + return; + auto it=list_view_text.get_selection()->get_selected(); + std::string row; + if(it) + it->get_value(0, row); + if(last_row==row) + return; + if(on_changed) + on_changed(row); + last_row=row; +} + SelectionDialogBase::~SelectionDialogBase() { text_view.get_buffer()->delete_mark(start_mark); } -void SelectionDialogBase::add_row(const std::string& row, const std::string& tooltip) { +void SelectionDialogBase::add_row(const std::string& row) { list_view_text.append(row); - if(tooltip.size()>0) - tooltip_texts[row]=tooltip; } void SelectionDialogBase::show() { shown=true; move(); window->show_all(); + + if(list_view_text.get_model()->children().size()>0) { + list_view_text.set_cursor(list_view_text.get_model()->get_path(list_view_text.get_model()->children().begin())); + cursor_changed(); + } } void SelectionDialogBase::hide() { if(!shown) return; + shown=false; window->hide(); - if(tooltips) - tooltips->hide(); if(on_hide) on_hide(); list_view_text.clear(); - shown=false; -} - -void SelectionDialogBase::update_tooltips() { - auto it=list_view_text.get_selection()->get_selected(); - if(it) { - std::string row; - it->get_value(0, row); - if(row!=last_row || last_row.size()==0) { - if(tooltips) - tooltips->hide(); - auto it=tooltip_texts.find(row); - if(it!=tooltip_texts.end()) { - auto tooltip_text=it->second; - if(tooltip_text.size()>0) { - tooltips=std::unique_ptr(new Tooltips()); - auto get_tooltip_buffer=[this, tooltip_text]() { - auto tooltip_buffer=Gtk::TextBuffer::create(text_view.get_buffer()->get_tag_table()); - tooltip_buffer->insert_with_tag(tooltip_buffer->get_insert()->get_iter(), tooltip_text, "def:note"); - return tooltip_buffer; - }; - tooltips->emplace_back(get_tooltip_buffer, text_view, text_view.get_buffer()->create_mark(start_mark->get_iter()), text_view.get_buffer()->create_mark(text_view.get_buffer()->get_insert()->get_iter())); - tooltips->show(true); - } - } - } - last_row=row; - } - else { - last_row=""; - if(tooltips) - tooltips->hide(); - } } void SelectionDialogBase::move() { @@ -178,10 +159,7 @@ void SelectionDialogBase::resize() { } } -SelectionDialog::SelectionDialog(Gtk::TextView& text_view, Glib::RefPtr start_mark, bool show_search_entry, bool use_markup) : SelectionDialogBase(text_view, start_mark, show_search_entry, use_markup) {} - -void SelectionDialog::show() { - SelectionDialogBase::show(); +SelectionDialog::SelectionDialog(Gtk::TextView& text_view, Glib::RefPtr start_mark, bool show_search_entry, bool use_markup) : SelectionDialogBase(text_view, start_mark, show_search_entry, use_markup) { std::shared_ptr search_key(new std::string()); auto filter_model=Gtk::TreeModelFilter::create(list_view_text.get_model()); @@ -214,6 +192,10 @@ void SelectionDialog::show() { *search_key=search_entry.get_text(); filter_model->refilter(); list_view_text.set_search_entry(search_entry); //TODO:Report the need of this to GTK's git (bug) + if(search_key->empty()) { + if(list_view_text.get_model()->children().size()>0) + list_view_text.set_cursor(list_view_text.get_model()->get_path(list_view_text.get_model()->children().begin())); + } }); search_entry.signal_event().connect([this](GdkEvent* event) { @@ -223,27 +205,17 @@ void SelectionDialog::show() { auto it=list_view_text.get_selection()->get_selected(); if(it) { it++; - if(it) { + if(it) list_view_text.set_cursor(list_view_text.get_model()->get_path(it)); - } } - else - list_view_text.set_cursor(list_view_text.get_model()->get_path(list_view_text.get_model()->children().begin())); return true; } if(key->keyval==GDK_KEY_Up && list_view_text.get_model()->children().size()>0) { auto it=list_view_text.get_selection()->get_selected(); if(it) { it--; - if(it) { + if(it) list_view_text.set_cursor(list_view_text.get_model()->get_path(it)); - } - } - else { - auto last_it=list_view_text.get_model()->children().end(); - last_it--; - if(last_it) - list_view_text.set_cursor(list_view_text.get_model()->get_path(last_it)); } return true; } @@ -271,11 +243,6 @@ void SelectionDialog::show() { hide(); return true; }); - - if(list_view_text.get_model()->children().size()>0) { - list_view_text.set_cursor(list_view_text.get_model()->get_path(list_view_text.get_model()->children().begin())); - update_tooltips(); - } } bool SelectionDialog::on_key_press(GdkEventKey* key) { @@ -283,27 +250,17 @@ bool SelectionDialog::on_key_press(GdkEventKey* key) { auto it=list_view_text.get_selection()->get_selected(); if(it) { it++; - if(it) { + if(it) list_view_text.set_cursor(list_view_text.get_model()->get_path(it)); - } } - else - list_view_text.set_cursor(list_view_text.get_model()->get_path(list_view_text.get_model()->children().begin())); return true; } if(key->keyval==GDK_KEY_Up && list_view_text.get_model()->children().size()>0) { auto it=list_view_text.get_selection()->get_selected(); if(it) { it--; - if(it) { + if(it) list_view_text.set_cursor(list_view_text.get_model()->get_path(it)); - } - } - else { - auto last_it=list_view_text.get_model()->children().end(); - last_it--; - if(last_it) - list_view_text.set_cursor(list_view_text.get_model()->get_path(last_it)); } return true; } @@ -319,13 +276,9 @@ bool SelectionDialog::on_key_press(GdkEventKey* key) { return false; } -CompletionDialog::CompletionDialog(Gtk::TextView& text_view, Glib::RefPtr start_mark) : SelectionDialogBase(text_view, start_mark, false, false) {} - -void CompletionDialog::show() { - SelectionDialogBase::show(); - +CompletionDialog::CompletionDialog(Gtk::TextView& text_view, Glib::RefPtr start_mark) : SelectionDialogBase(text_view, start_mark, false, false) { show_offset=text_view.get_buffer()->get_insert()->get_iter().get_offset(); - + std::shared_ptr search_key(new std::string()); auto filter_model=Gtk::TreeModelFilter::create(list_view_text.get_model()); if(show_offset==start_mark->get_iter().get_offset()) { @@ -365,11 +318,6 @@ void CompletionDialog::show() { search_entry.set_text(text); list_view_text.set_search_entry(search_entry); } - - if(list_view_text.get_model()->children().size()>0) { - list_view_text.set_cursor(list_view_text.get_model()->get_path(list_view_text.get_model()->children().begin())); - update_tooltips(); - } } void CompletionDialog::select(bool hide_window) { @@ -400,11 +348,10 @@ bool CompletionDialog::on_key_release(GdkEventKey* key) { search_entry.set_text(text); list_view_text.set_search_entry(search_entry); if(text=="") { - if(list_view_text.get_model()->children().size()>0) { + if(list_view_text.get_model()->children().size()>0) list_view_text.set_cursor(list_view_text.get_model()->get_path(list_view_text.get_model()->children().begin())); - } } - update_tooltips(); + cursor_changed(); } return false; } @@ -417,12 +364,9 @@ bool CompletionDialog::on_key_press(GdkEventKey* key) { if(row_in_entry) { text_view.get_buffer()->erase(start_mark->get_iter(), text_view.get_buffer()->get_insert()->get_iter()); row_in_entry=false; - if(key->keyval==GDK_KEY_BackSpace) { - update_tooltips(); + if(key->keyval==GDK_KEY_BackSpace) return true; - } } - update_tooltips(); return false; } if(key->keyval==GDK_KEY_Shift_L || key->keyval==GDK_KEY_Shift_R || key->keyval==GDK_KEY_Alt_L || key->keyval==GDK_KEY_Alt_R || key->keyval==GDK_KEY_Control_L || key->keyval==GDK_KEY_Control_R || key->keyval==GDK_KEY_Meta_L || key->keyval==GDK_KEY_Meta_R) @@ -433,12 +377,10 @@ bool CompletionDialog::on_key_press(GdkEventKey* key) { it++; if(it) { list_view_text.set_cursor(list_view_text.get_model()->get_path(it)); + cursor_changed(); } } - else - list_view_text.set_cursor(list_view_text.get_model()->get_path(list_view_text.get_model()->children().begin())); select(false); - update_tooltips(); return true; } if(key->keyval==GDK_KEY_Up && list_view_text.get_model()->children().size()>0) { @@ -447,16 +389,10 @@ bool CompletionDialog::on_key_press(GdkEventKey* key) { it--; if(it) { list_view_text.set_cursor(list_view_text.get_model()->get_path(it)); + cursor_changed(); } } - else { - auto last_it=list_view_text.get_model()->children().end(); - last_it--; - if(last_it) - list_view_text.set_cursor(list_view_text.get_model()->get_path(last_it)); - } select(false); - update_tooltips(); return true; } if(key->keyval==GDK_KEY_Return || key->keyval==GDK_KEY_ISO_Left_Tab || key->keyval==GDK_KEY_Tab) { diff --git a/src/selectiondialog.h b/src/selectiondialog.h index 7c6d901..732652f 100644 --- a/src/selectiondialog.h +++ b/src/selectiondialog.h @@ -3,7 +3,6 @@ #include "gtkmm.h" #include "logging.h" -#include "tooltips.h" #include class ListViewText : public Gtk::TreeView { @@ -29,19 +28,21 @@ class SelectionDialogBase { public: SelectionDialogBase(Gtk::TextView& text_view, Glib::RefPtr start_mark, bool show_search_entry, bool use_markup); ~SelectionDialogBase(); - virtual void add_row(const std::string& row, const std::string& tooltip=""); - virtual void show(); - virtual void hide(); - virtual void move(); + void add_row(const std::string& row); + void show(); + void hide(); + void move(); std::function on_hide; std::function on_select; + std::function on_changed; Glib::RefPtr start_mark; bool shown=false; protected: - virtual void resize(); - virtual void update_tooltips(); + void cursor_changed(); + + void resize(); Gtk::TextView& text_view; std::unique_ptr window; @@ -49,8 +50,7 @@ protected: ListViewText list_view_text; Gtk::Entry search_entry; bool show_search_entry; - std::unique_ptr tooltips; - std::unordered_map tooltip_texts; + std::string last_row; }; @@ -58,13 +58,11 @@ class SelectionDialog : public SelectionDialogBase { public: SelectionDialog(Gtk::TextView& text_view, Glib::RefPtr start_mark, bool show_search_entry=true, bool use_markup=false); bool on_key_press(GdkEventKey* key); - void show() override; }; class CompletionDialog : public SelectionDialogBase { public: CompletionDialog(Gtk::TextView& text_view, Glib::RefPtr start_mark); - void show() override; bool on_key_release(GdkEventKey* key); bool on_key_press(GdkEventKey* key); diff --git a/src/source.cc b/src/source.cc index 97413e7..05381cd 100644 --- a/src/source.cc +++ b/src/source.cc @@ -121,7 +121,22 @@ Source::View::View(const boost::filesystem::path &file_path, const boost::filesy spellcheck_checker=NULL; auto tag=get_buffer()->create_tag("spellcheck_error"); tag->property_underline()=Pango::Underline::UNDERLINE_ERROR; - + + auto mark_attr_debug_breakpoint=Gsv::MarkAttributes::create(); + Gdk::RGBA rgba; + rgba.set_red(1.0); + rgba.set_green(0.5); + rgba.set_blue(0.5); + rgba.set_alpha(0.3); + mark_attr_debug_breakpoint->set_background(rgba); + set_mark_attributes("debug_breakpoint", mark_attr_debug_breakpoint, 100); + auto mark_attr_debug_stop=Gsv::MarkAttributes::create(); + rgba.set_red(0.5); + rgba.set_green(0.5); + rgba.set_blue(1.0); + mark_attr_debug_stop->set_background(rgba); + set_mark_attributes("debug_stop", mark_attr_debug_stop, 101); + get_buffer()->signal_changed().connect([this](){ if(spellcheck_checker==NULL) return; @@ -667,6 +682,21 @@ void Source::View::paste() { } } +Gtk::TextIter Source::View::get_iter_for_dialog() { + auto iter=get_buffer()->get_insert()->get_iter(); + if(iter.get_line_offset()>=80) + iter=get_buffer()->get_iter_at_line(iter.get_line()); + Gdk::Rectangle visible_rect; + get_visible_rect(visible_rect); + Gdk::Rectangle iter_rect; + get_iter_location(iter, iter_rect); + iter_rect.set_width(1); + if(!visible_rect.intersects(iter_rect)) { + get_iter_at_location(iter, 0, visible_rect.get_y()+visible_rect.get_height()/3); + } + return iter; +} + void Source::View::set_status(const std::string &status) { this->status=status; if(on_update_status) diff --git a/src/source.h b/src/source.h index 35d6c98..b060e8b 100644 --- a/src/source.h +++ b/src/source.h @@ -87,6 +87,7 @@ namespace Source { std::unique_ptr autocomplete_dialog; std::unique_ptr selection_dialog; + Gtk::TextIter get_iter_for_dialog(); sigc::connection delayed_tooltips_connection; std::function on_update_status; diff --git a/src/source_clang.cc b/src/source_clang.cc index d2d5beb..b5a4bb8 100644 --- a/src/source_clang.cc +++ b/src/source_clang.cc @@ -2,6 +2,9 @@ #include "config.h" #include "terminal.h" #include "cmake.h" +#ifdef JUCI_ENABLE_DEBUG +#include "debug.h" +#endif namespace sigc { #ifndef SIGC_FUNCTORS_DEDUCE_RESULT_TYPE_WITH_DECLTYPE @@ -179,11 +182,7 @@ void Source::ClangViewParse::soft_reparse() { } std::vector Source::ClangViewParse::get_compilation_commands() { - boost::filesystem::path default_build_path; - if(boost::filesystem::exists(project_path/"CMakeLists.txt")) - default_build_path=CMake::get_default_build_path(project_path); - - clang::CompilationDatabase db(default_build_path.string()); + clang::CompilationDatabase db(CMake::get_default_build_path(project_path).string()); clang::CompileCommands commands(file_path.string(), db); std::vector cmds = commands.get_commands(); std::vector arguments; @@ -416,6 +415,33 @@ void Source::ClangViewParse::show_type_tooltips(const Gdk::Rectangle &rectangle) auto brief_comment=token.get_cursor().get_brief_comments(); if(brief_comment!="") tooltip_buffer->insert_with_tag(tooltip_buffer->get_insert()->get_iter(), "\n\n"+brief_comment, "def:note"); + +#ifdef JUCI_ENABLE_DEBUG + if(Debug::get().is_stopped()) { + auto location=token.get_cursor().get_referenced().get_source_location(); + Glib::ustring value_type="Value"; + Glib::ustring debug_value=Debug::get().get_value(token.get_spelling(), location.get_path(), location.get_offset().line, location.get_offset().index); + if(debug_value.empty()) { + value_type="Return value"; + auto cursor=token.get_cursor(); + auto offsets=cursor.get_source_range().get_offsets(); + debug_value=Debug::get().get_return_value(cursor.get_source_location().get_path(), offsets.first.line, offsets.first.index); + } + if(!debug_value.empty()) { + size_t pos=debug_value.find(" = "); + if(pos!=Glib::ustring::npos) { + Glib::ustring::iterator iter; + while(!debug_value.validate(iter)) { + auto next_char_iter=iter; + next_char_iter++; + debug_value.replace(iter, next_char_iter, "?"); + } + tooltip_buffer->insert_with_tag(tooltip_buffer->get_insert()->get_iter(), "\n\n"+value_type+": "+debug_value.substr(pos+3, debug_value.size()-(pos+3)-1), "def:note"); + } + } + } +#endif + return tooltip_buffer; }; @@ -658,11 +684,10 @@ Source::ClangViewParse(file_path, project_path, language), autocomplete_state(Au else { if(autocomplete_state==AutocompleteState::STARTING || autocomplete_state==AutocompleteState::RESTARTING) autocomplete_state=AutocompleteState::CANCELED; - else { - auto iter=get_buffer()->get_insert()->get_iter(); - if(last_keyval=='.' || last_keyval==':' || (last_keyval=='>' && iter.backward_char() && iter.backward_char() && *iter=='-')) - autocomplete_check(); - } + auto iter=get_buffer()->get_insert()->get_iter(); + iter.backward_chars(2); + if(last_keyval=='.' || (last_keyval==':' && *iter==':') || (last_keyval=='>' && *iter=='-')) + autocomplete_check(); } } }); @@ -703,26 +728,24 @@ Source::ClangViewParse(file_path, project_path, language), autocomplete_state(Au autocomplete_dialog_setup(); for (auto &data : autocomplete_data) { - std::stringstream ss; + std::string row; std::string return_value; for (auto &chunk : data.chunks) { - switch (chunk.kind) { - case clang::CompletionChunk_ResultType: - return_value = chunk.chunk; - break; - case clang::CompletionChunk_Informative: break; - default: ss << chunk.chunk; break; - } + if(chunk.kind==clang::CompletionChunk_ResultType) + return_value=chunk.chunk; + else if(chunk.kind!=clang::CompletionChunk_Informative) + row+=chunk.chunk; } - auto row=ss.str(); - auto row_insert_on_selection=row; + data.chunks.clear(); if (!row.empty()) { + auto row_insert_on_selection=row; if(!return_value.empty()) row+=" --> " + return_value; - autocomplete_dialog_rows[row] = row_insert_on_selection; - autocomplete_dialog->add_row(row, data.brief_comments); + autocomplete_dialog_rows[row] = std::pair(std::move(row_insert_on_selection), std::move(data.brief_comments)); + autocomplete_dialog->add_row(row); } } + autocomplete_data.clear(); set_status(""); autocomplete_state=AutocompleteState::IDLE; if (!autocomplete_dialog_rows.empty()) { @@ -772,11 +795,13 @@ void Source::ClangViewAutocomplete::autocomplete_dialog_setup() { autocomplete_dialog_rows.clear(); autocomplete_dialog->on_hide=[this](){ get_source_buffer()->end_user_action(); + autocomplete_tooltips.hide(); + autocomplete_tooltips.clear(); parsed=false; soft_reparse(); }; autocomplete_dialog->on_select=[this](const std::string& selected, bool hide_window) { - auto row = autocomplete_dialog_rows.at(selected); + auto row = autocomplete_dialog_rows.at(selected).first; get_buffer()->erase(autocomplete_dialog->start_mark->get_iter(), get_buffer()->get_insert()->get_iter()); auto iter=get_buffer()->get_insert()->get_iter(); if(*iter=='<' || *iter=='(') { @@ -825,6 +850,32 @@ void Source::ClangViewAutocomplete::autocomplete_dialog_setup() { } } }; + + autocomplete_dialog->on_changed=[this](const std::string &selected) { + if(selected.empty()) { + autocomplete_tooltips.hide(); + return; + } + auto tooltip=std::make_shared(autocomplete_dialog_rows.at(selected).second); + if(tooltip->empty()) { + autocomplete_tooltips.hide(); + } + else { + autocomplete_tooltips.clear(); + auto create_tooltip_buffer=[this, tooltip]() { + auto tooltip_buffer=Gtk::TextBuffer::create(get_buffer()->get_tag_table()); + + tooltip_buffer->insert_with_tag(tooltip_buffer->get_insert()->get_iter(), *tooltip, "def:note"); + + return tooltip_buffer; + }; + + auto iter=autocomplete_dialog->start_mark->get_iter(); + autocomplete_tooltips.emplace_back(create_tooltip_buffer, *this, get_buffer()->create_mark(iter), get_buffer()->create_mark(iter)); + + autocomplete_tooltips.show(true); + } + }; } void Source::ClangViewAutocomplete::autocomplete_check() { @@ -1184,15 +1235,7 @@ Source::ClangViewAutocomplete(file_path, project_path, language) { goto_method=[this](){ if(parsed) { - auto iter=get_buffer()->get_insert()->get_iter(); - Gdk::Rectangle visible_rect; - get_visible_rect(visible_rect); - Gdk::Rectangle iter_rect; - get_iter_location(iter, iter_rect); - iter_rect.set_width(1); - if(!visible_rect.intersects(iter_rect)) { - get_iter_at_location(iter, 0, visible_rect.get_y()+visible_rect.get_height()/3); - } + auto iter=get_iter_for_dialog(); selection_dialog=std::unique_ptr(new SelectionDialog(*this, get_buffer()->create_mark(iter), true, true)); auto rows=std::make_shared >(); auto methods=clang_tokens->get_cxx_methods(); diff --git a/src/source_clang.h b/src/source_clang.h index f4d26d6..93b226d 100644 --- a/src/source_clang.h +++ b/src/source_clang.h @@ -101,8 +101,9 @@ namespace Source { void autocomplete_check(); void autocomplete(); std::vector autocomplete_data; - std::unordered_map autocomplete_dialog_rows; + std::unordered_map > autocomplete_dialog_rows; std::vector autocomplete_get_suggestions(const std::string &buffer, int line_number, int column); + Tooltips autocomplete_tooltips; Glib::Dispatcher autocomplete_done; Glib::Dispatcher autocomplete_restart; Glib::Dispatcher autocomplete_error; diff --git a/src/terminal.cc b/src/terminal.cc index 546328c..87c5068 100644 --- a/src/terminal.cc +++ b/src/terminal.cc @@ -2,6 +2,9 @@ #include #include "logging.h" #include "config.h" +#ifdef JUCI_ENABLE_DEBUG +#include "debug.h" +#endif Terminal::InProgress::InProgress(const std::string& start_msg): stop(false) { waiting_print.connect([this](){ @@ -243,7 +246,11 @@ void Terminal::async_print(int line_nr, const std::string &message) { bool Terminal::on_key_press_event(GdkEventKey *event) { processes_mutex.lock(); - if(processes.size()>0) { + bool debug_is_running=false; +#ifdef JUCI_ENABLE_DEBUG + debug_is_running=Debug::get().is_running(); +#endif + if(processes.size()>0 || debug_is_running) { get_buffer()->place_cursor(get_buffer()->end()); auto unicode=gdk_keyval_to_unicode(event->keyval); char chr=(char)unicode; @@ -261,7 +268,13 @@ bool Terminal::on_key_press_event(GdkEventKey *event) { } else if(event->keyval==GDK_KEY_Return) { stdin_buffer+='\n'; - processes.back()->write(stdin_buffer); + if(debug_is_running) { +#ifdef JUCI_ENABLE_DEBUG + Debug::get().write(stdin_buffer); +#endif + } + else + processes.back()->write(stdin_buffer); get_buffer()->insert_at_cursor(stdin_buffer.substr(stdin_buffer.size()-1)); stdin_buffer.clear(); } diff --git a/src/tooltips.cc b/src/tooltips.cc index 9874c6b..b177f62 100644 --- a/src/tooltips.cc +++ b/src/tooltips.cc @@ -143,6 +143,7 @@ void Tooltips::show(const Gdk::Rectangle& rectangle, bool disregard_drawn) { if(rectangle.intersects(tooltip.activation_rectangle)) { tooltip.adjust(disregard_drawn); tooltip.window->show_all(); + shown=true; } else if(tooltip.window) tooltip.window->hide(); @@ -154,6 +155,7 @@ void Tooltips::show(bool disregard_drawn) { tooltip.update(); tooltip.adjust(disregard_drawn); tooltip.window->show_all(); + shown=true; } } @@ -162,4 +164,5 @@ void Tooltips::hide() { if(tooltip.window) tooltip.window->hide(); } + shown=false; } diff --git a/src/tooltips.h b/src/tooltips.h index 4f21953..66d87a9 100644 --- a/src/tooltips.h +++ b/src/tooltips.h @@ -30,6 +30,7 @@ public: static void init() {drawn_tooltips_rectangle=Gdk::Rectangle();} void show(const Gdk::Rectangle& rectangle, bool disregard_drawn=false); void show(bool disregard_drawn=false); + bool shown=false; void hide(); void clear() {tooltip_list.clear();}; @@ -37,10 +38,10 @@ public: void emplace_back(Ts&&... params) { tooltip_list.emplace_back(std::forward(params)...); } - + static Gdk::Rectangle drawn_tooltips_rectangle; - private: +private: std::list tooltip_list; }; diff --git a/src/window.cc b/src/window.cc index 38896f4..2c55f69 100644 --- a/src/window.cc +++ b/src/window.cc @@ -6,6 +6,9 @@ //#include "api.h" #include "dialogs.h" #include "filesystem.h" +#ifdef JUCI_ENABLE_DEBUG +#include "debug.h" +#endif namespace sigc { #ifndef SIGC_FUNCTORS_DEDUCE_RESULT_TYPE_WITH_DECLTYPE @@ -21,7 +24,7 @@ namespace sigc { #endif } -Window::Window() : compiling(false) { +Window::Window() : compiling(false), debugging(false) { JDEBUG("start"); set_title("juCi++"); set_events(Gdk::POINTER_MOTION_MASK|Gdk::FOCUS_CHANGE_MASK|Gdk::SCROLL_MASK|Gdk::LEAVE_NOTIFY_MASK); @@ -46,6 +49,12 @@ Window::Window() : compiling(false) { terminal_vbox.pack_start(terminal_scrolled_window); info_and_status_hbox.pack_start(notebook.info, Gtk::PACK_SHRINK); +#if GTK_VERSION_GE(3, 12) + info_and_status_hbox.set_center_widget(debug_status_label); +#else + debug_status_label.set_halign(Gtk::Align::ALIGN_CENTER); + info_and_status_hbox.pack_start(debug_status_label); +#endif info_and_status_hbox.pack_end(notebook.status, Gtk::PACK_SHRINK); terminal_vbox.pack_end(info_and_status_hbox, Gtk::PACK_SHRINK); vpaned.pack2(terminal_vbox, true, true); @@ -112,6 +121,74 @@ Window::Window() : compiling(false) { about.hide(); }); + debug_update_stop.connect([this](){ + debug_stop_mutex.lock(); + for(int c=0;cfile_path==debug_last_stop_file_path) { + view->get_source_buffer()->remove_source_marks(view->get_buffer()->begin(), view->get_buffer()->end(), "debug_stop"); + break; + } + } + //Add debug stop source mark + for(int c=0;cfile_path==debug_stop.first) { + if(debug_stop.second.first-1get_buffer()->get_line_count()) { + view->get_source_buffer()->create_source_mark("debug_stop", view->get_buffer()->get_iter_at_line(debug_stop.second.first-1)); + debug_last_stop_file_path=debug_stop.first; + } + break; + } + } + if(notebook.get_current_page()!=-1) + notebook.get_current_view()->get_buffer()->place_cursor(notebook.get_current_view()->get_buffer()->get_insert()->get_iter()); + debug_stop_mutex.unlock(); + }); + +#ifdef JUCI_ENABLE_DEBUG + auto &menu=Menu::get(); + menu.actions["debug_stop"]->set_enabled(false); + menu.actions["debug_kill"]->set_enabled(false); + menu.actions["debug_step_over"]->set_enabled(false); + menu.actions["debug_step_into"]->set_enabled(false); + menu.actions["debug_step_out"]->set_enabled(false); + menu.actions["debug_backtrace"]->set_enabled(false); + menu.actions["debug_show_variables"]->set_enabled(false); + menu.actions["debug_run_command"]->set_enabled(false); + menu.actions["debug_goto_stop"]->set_enabled(false); +#endif + debug_update_status.connect([this](){ + debug_status_mutex.lock(); + if(debug_status.empty()) { + debug_status_label.set_text(""); + auto &menu=Menu::get(); + menu.actions["debug_stop"]->set_enabled(false); + menu.actions["debug_kill"]->set_enabled(false); + menu.actions["debug_step_over"]->set_enabled(false); + menu.actions["debug_step_into"]->set_enabled(false); + menu.actions["debug_step_out"]->set_enabled(false); + menu.actions["debug_backtrace"]->set_enabled(false); + menu.actions["debug_show_variables"]->set_enabled(false); + menu.actions["debug_run_command"]->set_enabled(false); + menu.actions["debug_goto_stop"]->set_enabled(false); + } + else { + debug_status_label.set_text("debug: "+debug_status); + auto &menu=Menu::get(); + menu.actions["debug_stop"]->set_enabled(); + menu.actions["debug_kill"]->set_enabled(); + menu.actions["debug_step_over"]->set_enabled(); + menu.actions["debug_step_into"]->set_enabled(); + menu.actions["debug_step_out"]->set_enabled(); + menu.actions["debug_backtrace"]->set_enabled(); + menu.actions["debug_show_variables"]->set_enabled(); + menu.actions["debug_run_command"]->set_enabled(); + menu.actions["debug_goto_stop"]->set_enabled(); + } + debug_status_mutex.unlock(); + }); + about.set_version(Config::get().window.version); about.set_authors({"(in order of appearance)", "Ted Johan Kristoffersen", @@ -126,6 +203,22 @@ Window::Window() : compiling(false) { JDEBUG("end"); } // Window constructor +std::unique_ptr Window::get_cmake() { + boost::filesystem::path path; + if(notebook.get_current_page()!=-1) + path=notebook.get_current_view()->file_path.parent_path(); + else + path=Directories::get().current_path; + if(path.empty()) + return nullptr; + auto cmake=std::unique_ptr(new CMake(path)); + if(cmake->project_path.empty()) + return nullptr; + if(!CMake::create_default_build(cmake->project_path)) + return nullptr; + return cmake; +} + void Window::configure() { Config::get().load(); auto style_context = Gtk::StyleContext::create(); @@ -405,10 +498,10 @@ void Window::set_menu_actions() { auto end_line_index=iter.get_line_index(); index=std::min(index, end_line_index); + view->get_buffer()->place_cursor(view->get_buffer()->get_iter_at_line_index(line, index)); while(g_main_context_pending(NULL)) g_main_context_iteration(NULL, false); if(notebook.get_current_page()!=-1 && notebook.get_current_view()==view) { - view->get_buffer()->place_cursor(view->get_buffer()->get_iter_at_line_index(line, index)); view->scroll_to(view->get_buffer()->get_insert(), 0.0, 1.0, 0.5); view->delayed_tooltips_connection.disconnect(); } @@ -423,15 +516,7 @@ void Window::set_menu_actions() { if(current_view->get_token && current_view->get_usages) { auto token=current_view->get_token(); if(token) { - auto iter=current_view->get_buffer()->get_insert()->get_iter(); - Gdk::Rectangle visible_rect; - current_view->get_visible_rect(visible_rect); - Gdk::Rectangle iter_rect; - current_view->get_iter_location(iter, iter_rect); - iter_rect.set_width(1); - if(!visible_rect.intersects(iter_rect)) { - current_view->get_iter_at_location(iter, 0, visible_rect.get_y()+visible_rect.get_height()/3); - } + auto iter=current_view->get_iter_for_dialog(); current_view->selection_dialog=std::unique_ptr(new SelectionDialog(*current_view, current_view->get_buffer()->create_mark(iter), true, true)); auto rows=std::make_shared >(); @@ -505,98 +590,115 @@ void Window::set_menu_actions() { } }); - menu.add_action("compile_and_run", [this]() { - if(compiling) + menu.add_action("project_set_run_arguments", [this]() { + auto cmake=get_cmake(); + if(!cmake) return; - boost::filesystem::path cmake_path; - if(notebook.get_current_page()!=-1) - cmake_path=notebook.get_current_view()->file_path.parent_path(); - else - cmake_path=Directories::get().current_path; - if(cmake_path.empty()) - return; - CMake cmake(cmake_path); - auto executables = cmake.get_functions_parameters("add_executable"); - //Attempt to find executable based add_executable files and opened tab - boost::filesystem::path executable_path; - if(notebook.get_current_page()!=-1) { - for(auto &executable: executables) { - if(executable.second.size()>1) { - for(size_t c=1;cfile_path.filename()) { - executable_path=executable.first.parent_path()/executable.second[0]; - break; - } - } + auto project_path=std::make_shared(cmake->project_path); + auto run_arguments_it=project_run_arguments.find(project_path->string()); + std::string run_arguments; + if(run_arguments_it!=project_run_arguments.end()) + run_arguments=run_arguments_it->second; + + if(run_arguments.empty()) { + auto executable=cmake->get_executable(notebook.get_current_page()!=-1?notebook.get_current_view()->file_path:"").string(); + + if(executable!="") { + auto project_path=cmake->project_path; + auto default_build_path=CMake::get_default_build_path(project_path); + if(!default_build_path.empty()) { + size_t pos=executable.find(project_path.string()); + if(pos!=std::string::npos) + executable.replace(pos, project_path.string().size(), default_build_path.string()); } - if(!executable_path.empty()) - break; + run_arguments=filesystem::escape_argument(executable); } + else + run_arguments=filesystem::escape_argument(CMake::get_default_build_path(cmake->project_path)); } - if(executable_path.empty() && executables.size()>0 && executables[0].second.size()>0) - executable_path=executables[0].first.parent_path()/executables[0].second[0]; - if(cmake.project_path!="") { - if(executable_path!="") { - auto project_path=cmake.project_path; - auto default_build_path=CMake::get_default_build_path(project_path); - if(default_build_path.empty()) - return; - compiling=true; - auto executable_path_string=executable_path.string(); - size_t pos=executable_path_string.find(project_path.string()); - if(pos!=std::string::npos) { - executable_path_string.replace(pos, project_path.string().size(), default_build_path.string()); - executable_path=executable_path_string; - } - Terminal::get().print("Compiling and running "+executable_path.string()+"\n"); - Terminal::get().async_process(Config::get().terminal.make_command, default_build_path, [this, executable_path, default_build_path](int exit_status){ - compiling=false; - if(exit_status==EXIT_SUCCESS) { - auto executable_path_spaces_fixed=executable_path.string(); - char last_char=0; - for(size_t c=0;cupdate=[label_it](int state, const std::string& message){ + label_it->set_text("Set empty to let juCi++ deduce executable"); + }; + label_it->update(0, ""); + entry_box.entries.emplace_back(run_arguments, [this, project_path](const std::string& content){ + project_run_arguments[project_path->string()]=content; + entry_box.hide(); + }, 50); + auto entry_it=entry_box.entries.begin(); + entry_it->set_placeholder_text("Project: Set Run Arguments"); + entry_box.buttons.emplace_back("Project: set run arguments", [this, entry_it](){ + entry_it->activate(); + }); + entry_box.show(); + }); + menu.add_action("compile_and_run", [this]() { + if(compiling) + return; + + auto cmake=get_cmake(); + if(!cmake) + return; + auto project_path=cmake->project_path; + + auto default_build_path=CMake::get_default_build_path(project_path); + if(default_build_path.empty()) + return; + + auto run_arguments_it=project_run_arguments.find(project_path.string()); + std::string run_arguments; + if(run_arguments_it!=project_run_arguments.end()) + run_arguments=run_arguments_it->second; + + std::string command; + if(!run_arguments.empty()) { + command=run_arguments; + } + else { + command=cmake->get_executable(notebook.get_current_page()!=-1?notebook.get_current_view()->file_path:"").string(); + if(command.empty()) { Terminal::get().print("Could not find add_executable in the following paths:\n"); - for(auto &path: cmake.paths) + for(auto &path: cmake->paths) Terminal::get().print(" "+path.string()+"\n"); + Terminal::get().print("Solution: either use Project Set Run Arguments, or open a source file within a directory where add_executable is set.\n", true); + return; } + size_t pos=command.find(project_path.string()); + if(pos!=std::string::npos) + command.replace(pos, project_path.string().size(), default_build_path.string()); + command=filesystem::escape_argument(command); } + + compiling=true; + Terminal::get().print("Compiling and running "+command+"\n"); + Terminal::get().async_process(Config::get().terminal.make_command, default_build_path, [this, command, default_build_path](int exit_status){ + compiling=false; + if(exit_status==EXIT_SUCCESS) { + Terminal::get().async_process(command, default_build_path, [this, command](int exit_status){ + Terminal::get().async_print(command+" returned: "+std::to_string(exit_status)+'\n'); + }); + } + }); }); menu.add_action("compile", [this]() { if(compiling) return; - boost::filesystem::path cmake_path; - if(notebook.get_current_page()!=-1) - cmake_path=notebook.get_current_view()->file_path.parent_path(); - else - cmake_path=Directories::get().current_path; - if(cmake_path.empty()) + auto cmake=get_cmake(); + if(!cmake) return; - CMake cmake(cmake_path); - if(cmake.project_path!="") { - auto default_build_path=CMake::get_default_build_path(cmake.project_path); - if(default_build_path.empty()) - return; - compiling=true; - Terminal::get().print("Compiling project "+cmake.project_path.string()+"\n"); - Terminal::get().async_process(Config::get().terminal.make_command, default_build_path, [this](int exit_status){ - compiling=false; - }); - } + + auto default_build_path=CMake::get_default_build_path(cmake->project_path); + if(default_build_path.empty()) + return; + compiling=true; + Terminal::get().print("Compiling project "+cmake->project_path.string()+"\n"); + Terminal::get().async_process(Config::get().terminal.make_command, default_build_path, [this](int exit_status){ + compiling=false; + }); }); menu.add_action("run_command", [this]() { @@ -634,6 +736,358 @@ void Window::set_menu_actions() { Terminal::get().kill_last_async_process(true); }); +#ifdef JUCI_ENABLE_DEBUG + menu.add_action("debug_set_run_arguments", [this]() { + auto cmake=get_cmake(); + if(!cmake) + return; + + auto project_path=std::make_shared(cmake->project_path); + auto run_arguments_it=debug_run_arguments.find(project_path->string()); + std::string run_arguments; + if(run_arguments_it!=debug_run_arguments.end()) + run_arguments=run_arguments_it->second; + + if(run_arguments.empty()) { + auto executable=cmake->get_executable(notebook.get_current_page()!=-1?notebook.get_current_view()->file_path:"").string(); + + if(executable!="") { + auto project_path=cmake->project_path; + auto debug_build_path=CMake::get_debug_build_path(project_path); + if(!debug_build_path.empty()) { + size_t pos=executable.find(project_path.string()); + if(pos!=std::string::npos) + executable.replace(pos, project_path.string().size(), debug_build_path.string()); + } + run_arguments=filesystem::escape_argument(executable); + } + else + run_arguments=filesystem::escape_argument(CMake::get_debug_build_path(cmake->project_path)); + } + + entry_box.clear(); + entry_box.labels.emplace_back(); + auto label_it=entry_box.labels.begin(); + label_it->update=[label_it](int state, const std::string& message){ + label_it->set_text("Set empty to let juCi++ deduce executable"); + }; + label_it->update(0, ""); + entry_box.entries.emplace_back(run_arguments, [this, project_path](const std::string& content){ + debug_run_arguments[project_path->string()]=content; + entry_box.hide(); + }, 50); + auto entry_it=entry_box.entries.begin(); + entry_it->set_placeholder_text("Project: Set Run Arguments"); + entry_box.buttons.emplace_back("Project: set run arguments", [this, entry_it](){ + entry_it->activate(); + }); + entry_box.show(); + }); + menu.add_action("debug_start_continue", [this](){ + if(debugging) { + Debug::get().continue_debug(); + return; + } + + auto cmake=get_cmake(); + if(!cmake) + return; + auto project_path=cmake->project_path; + + auto debug_build_path=CMake::get_debug_build_path(project_path); + if(debug_build_path.empty()) + return; + if(!CMake::create_debug_build(project_path)) + return; + + auto run_arguments_it=debug_run_arguments.find(project_path.string()); + std::string run_arguments; + if(run_arguments_it!=debug_run_arguments.end()) + run_arguments=run_arguments_it->second; + + std::string command; + if(!run_arguments.empty()) { + command=run_arguments; + } + else { + command=cmake->get_executable(notebook.get_current_page()!=-1?notebook.get_current_view()->file_path:"").string(); + if(command.empty()) { + Terminal::get().print("Could not find add_executable in the following paths:\n"); + for(auto &path: cmake->paths) + Terminal::get().print(" "+path.string()+"\n"); + Terminal::get().print("Solution: either use Debug Set Run Arguments, or open a source file within a directory where add_executable is set.\n", true); + return; + } + size_t pos=command.find(project_path.string()); + if(pos!=std::string::npos) + command.replace(pos, project_path.string().size(), debug_build_path.string()); + command=filesystem::escape_argument(command); + } + + auto breakpoints=std::make_shared > >(); + for(int c=0;cproject_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); + } + } + + debugging=true; + Terminal::get().print("Compiling and debugging "+command+"\n"); + Terminal::get().async_process(Config::get().terminal.make_command, debug_build_path, [this, breakpoints, command, debug_build_path](int exit_status){ + if(exit_status!=EXIT_SUCCESS) + debugging=false; + else { + debug_start_mutex.lock(); + Debug::get().start(command, debug_build_path, breakpoints, [this, command](int exit_status){ + debugging=false; + Terminal::get().async_print(command+" returned: "+std::to_string(exit_status)+'\n'); + }, [this](const std::string &status) { + debug_status_mutex.lock(); + debug_status=status; + debug_status_mutex.unlock(); + debug_update_status(); + }, [this](const boost::filesystem::path &file_path, int line_nr, int line_index) { + debug_stop_mutex.lock(); + debug_stop.first=file_path; + debug_stop.second.first=line_nr; + debug_stop.second.second=line_index; + debug_stop_mutex.unlock(); + debug_update_stop(); + //Remove debug stop source mark + }); + debug_start_mutex.unlock(); + } + }); + }); + menu.add_action("debug_stop", [this]() { + if(debugging) { + Debug::get().stop(); + } + }); + menu.add_action("debug_kill", [this]() { + if(debugging) { + Debug::get().kill(); + } + }); + menu.add_action("debug_step_over", [this]() { + if(debugging) + Debug::get().step_over(); + }); + menu.add_action("debug_step_into", [this]() { + if(debugging) + Debug::get().step_into(); + }); + menu.add_action("debug_step_out", [this]() { + if(debugging) + Debug::get().step_out(); + }); + menu.add_action("debug_backtrace", [this]() { + if(debugging && notebook.get_current_page()!=-1) { + auto backtrace=Debug::get().get_backtrace(); + + auto view=notebook.get_current_view(); + auto iter=view->get_iter_for_dialog(); + view->selection_dialog=std::unique_ptr(new SelectionDialog(*view, view->get_buffer()->create_mark(iter), true, true)); + auto rows=std::make_shared >(); + if(backtrace.size()==0) + return; + + for(auto &frame: backtrace) { + std::string row=""+frame.module_filename+""; + + //Shorten frame.function_name if it is too long + if(frame.function_name.size()>120) { + frame.function_name=frame.function_name.substr(0, 58)+"...."+frame.function_name.substr(frame.function_name.size()-58); + } + if(frame.file_path.empty()) + row+=" - "+Glib::Markup::escape_text(frame.function_name); + else { + auto file_path=boost::filesystem::path(frame.file_path).filename().string(); + row+=":"+Glib::Markup::escape_text(file_path)+":"+std::to_string(frame.line_nr)+" - "+Glib::Markup::escape_text(frame.function_name); + } + (*rows)[row]=frame; + view->selection_dialog->add_row(row); + } + + view->selection_dialog->on_select=[this, rows](const std::string& selected, bool hide_window) { + auto frame=rows->at(selected); + if(!frame.file_path.empty()) { + notebook.open(frame.file_path); + if(notebook.get_current_page()!=-1) { + auto view=notebook.get_current_view(); + + Debug::get().select_frame(frame.index); + + view->get_buffer()->place_cursor(view->get_buffer()->get_iter_at_line_index(frame.line_nr-1, frame.line_index-1)); + + while(g_main_context_pending(NULL)) + g_main_context_iteration(NULL, false); + if(notebook.get_current_page()!=-1 && notebook.get_current_view()==view) + view->scroll_to(view->get_buffer()->get_insert(), 0.0, 1.0, 0.5); + } + } + }; + view->selection_dialog->show(); + } + }); + menu.add_action("debug_show_variables", [this]() { + if(debugging && notebook.get_current_page()!=-1) { + auto variables=Debug::get().get_variables(); + + auto view=notebook.get_current_view(); + auto iter=view->get_iter_for_dialog(); + view->selection_dialog=std::unique_ptr(new SelectionDialog(*view, view->get_buffer()->create_mark(iter), true, true)); + auto rows=std::make_shared >(); + if(variables.size()==0) + return; + + for(auto &variable: variables) { + 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)+" - "+Glib::Markup::escape_text(variable.name)+""; + + (*rows)[row]=variable; + view->selection_dialog->add_row(row); + } + + view->selection_dialog->on_select=[this, rows](const std::string& selected, bool hide_window) { + auto variable=rows->at(selected); + if(!variable.file_path.empty()) { + notebook.open(variable.file_path); + if(notebook.get_current_page()!=-1) { + auto view=notebook.get_current_view(); + + Debug::get().select_frame(variable.frame_index, variable.thread_index_id); + + view->get_buffer()->place_cursor(view->get_buffer()->get_iter_at_line_index(variable.line_nr-1, variable.line_index-1)); + + while(g_main_context_pending(NULL)) + g_main_context_iteration(NULL, false); + if(notebook.get_current_page()!=-1 && notebook.get_current_view()==view) + view->scroll_to(view->get_buffer()->get_insert(), 0.0, 1.0, 0.5); + } + } + }; + + view->selection_dialog->on_hide=[this]() { + debug_variable_tooltips.hide(); + debug_variable_tooltips.clear(); + }; + + view->selection_dialog->on_changed=[this, rows, iter](const std::string &selected) { + if(selected.empty()) { + debug_variable_tooltips.hide(); + return; + } + if(notebook.get_current_page()!=-1) { + auto view=notebook.get_current_view(); + debug_variable_tooltips.clear(); + auto create_tooltip_buffer=[this, rows, view, selected]() { + auto variable=rows->at(selected); + auto tooltip_buffer=Gtk::TextBuffer::create(view->get_buffer()->get_tag_table()); + + Glib::ustring value=variable.value; + if(!value.empty()) { + Glib::ustring::iterator iter; + while(!value.validate(iter)) { + auto next_char_iter=iter; + next_char_iter++; + value.replace(iter, next_char_iter, "?"); + } + tooltip_buffer->insert_with_tag(tooltip_buffer->get_insert()->get_iter(), value.substr(0, value.size()-1), "def:note"); + } + + return tooltip_buffer; + }; + + debug_variable_tooltips.emplace_back(create_tooltip_buffer, *view, view->get_buffer()->create_mark(iter), view->get_buffer()->create_mark(iter)); + + debug_variable_tooltips.show(true); + } + }; + + view->selection_dialog->show(); + } + }); + menu.add_action("debug_run_command", [this]() { + entry_box.clear(); + entry_box.entries.emplace_back(last_run_debug_command, [this](const std::string& content){ + if(content!="") { + if(debugging) { + auto command_return=Debug::get().run_command(content); + Terminal::get().async_print(command_return.first); + Terminal::get().async_print(command_return.second, true); + } + last_run_debug_command=content; + } + entry_box.hide(); + }, 30); + auto entry_it=entry_box.entries.begin(); + entry_it->set_placeholder_text("Debug Command"); + entry_box.buttons.emplace_back("Run debug command", [this, entry_it](){ + entry_it->activate(); + }); + entry_box.show(); + }); + menu.add_action("debug_toggle_breakpoint", [this](){ + if(notebook.get_current_page()!=-1) { + auto view=notebook.get_current_view(); + bool debug_is_stopped_or_running=Debug::get().is_stopped() || Debug::get().is_running(); + if(Debug::get().is_invalid() || debug_is_stopped_or_running) { + auto line_nr=view->get_buffer()->get_insert()->get_iter().get_line(); + + if(view->get_source_buffer()->get_source_marks_at_line(line_nr, "debug_breakpoint").size()>0) { + auto start_iter=view->get_buffer()->get_iter_at_line(line_nr); + auto end_iter=start_iter; + while(!end_iter.ends_line() && end_iter.forward_char()) {} + view->get_source_buffer()->remove_source_marks(start_iter, end_iter, "debug_breakpoint"); + if(debug_is_stopped_or_running) + Debug::get().remove_breakpoint(view->file_path, line_nr+1, view->get_buffer()->get_line_count()+1); + } + else { + view->get_source_buffer()->create_source_mark("debug_breakpoint", view->get_buffer()->get_insert()->get_iter()); + if(debug_is_stopped_or_running) + Debug::get().add_breakpoint(view->file_path, line_nr+1); + } + } + } + }); + menu.add_action("debug_goto_stop", [this](){ + if(debugging) { + debug_stop_mutex.lock(); + auto debug_stop_copy=debug_stop; + debug_stop_mutex.unlock(); + if(!debug_stop_copy.first.empty()) { + notebook.open(debug_stop_copy.first); + if(notebook.get_current_page()!=-1) { + auto view=notebook.get_current_view(); + + int line_nr=debug_stop_copy.second.first-1; + int line_index=debug_stop_copy.second.second-1; + if(line_nrget_buffer()->get_line_count()) { + auto iter=view->get_buffer()->get_iter_at_line(line_nr); + auto end_line_iter=iter; + while(!iter.ends_line() && iter.forward_char()) {} + auto line=view->get_buffer()->get_text(iter, end_line_iter); + if(static_cast(line_index)>=line.bytes()) + line_index=0; + view->get_buffer()->place_cursor(view->get_buffer()->get_iter_at_line_index(line_nr, line_index)); + + while(g_main_context_pending(NULL)) + g_main_context_iteration(NULL, false); + if(notebook.get_current_page()!=-1 && notebook.get_current_view()==view) + view->scroll_to(view->get_buffer()->get_insert(), 0.0, 1.0, 0.5); + } + debug_update_stop(); + } + } + } + }); +#endif + menu.add_action("next_tab", [this]() { if(notebook.get_current_page()!=-1) { notebook.open(notebook.get_view((notebook.get_current_page()+1)%notebook.size())->file_path); @@ -673,6 +1127,17 @@ void Window::activate_menu_items(bool activate) { menu.actions["source_rename"]->set_enabled(activate ? static_cast(notebook.get_current_view()->rename_similar_tokens) : false); menu.actions["source_goto_next_diagnostic"]->set_enabled(activate ? static_cast(notebook.get_current_view()->goto_next_diagnostic) : false); menu.actions["source_apply_fix_its"]->set_enabled(activate ? static_cast(notebook.get_current_view()->apply_fix_its) : false); +#ifdef JUCI_ENABLE_DEBUG + if(notebook.get_current_page()!=-1) { + auto view=notebook.get_current_view(); + if(view->language && (view->language->get_id()=="c" || view->language->get_id()=="cpp" || view->language->get_id()=="objc" || view->language->get_id()=="chdr" || view->language->get_id()=="cpphdr")) + menu.actions["debug_toggle_breakpoint"]->set_enabled(true); + else + menu.actions["debug_toggle_breakpoint"]->set_enabled(false); + } + else + menu.actions["debug_toggle_breakpoint"]->set_enabled(false); +#endif } bool Window::on_key_press_event(GdkEventKey *event) { @@ -716,6 +1181,11 @@ bool Window::on_delete_event(GdkEventAny *event) { return true; } Terminal::get().kill_async_processes(); +#ifdef JUCI_ENABLE_DEBUG + debug_start_mutex.lock(); + Debug::get().delete_debug(); + debug_start_mutex.unlock(); +#endif return false; } @@ -879,12 +1349,11 @@ void Window::goto_line_entry() { if(line>0 && line<=view->get_buffer()->get_line_count()) { line--; + view->get_buffer()->place_cursor(view->get_buffer()->get_iter_at_line(line)); while(g_main_context_pending(NULL)) g_main_context_iteration(NULL, false); - if(notebook.get_current_page()!=-1 && notebook.get_current_view()==view) { - view->get_buffer()->place_cursor(view->get_buffer()->get_iter_at_line(line)); + if(notebook.get_current_page()!=-1 && notebook.get_current_view()==view) view->scroll_to(view->get_buffer()->get_insert(), 0.0, 1.0, 0.5); - } } } catch(const std::exception &e) {} diff --git a/src/window.h b/src/window.h index 2587348..a3485a2 100644 --- a/src/window.h +++ b/src/window.h @@ -4,6 +4,8 @@ #include "gtkmm.h" #include "entrybox.h" #include "notebook.h" +#include "cmake.h" +#include "tooltips.h" #include class Window : public Gtk::ApplicationWindow { @@ -31,7 +33,26 @@ private: Gtk::HBox info_and_status_hbox; Gtk::AboutDialog about; EntryBox entry_box; + std::atomic compiling; + + std::atomic debugging; + Gtk::Label debug_status_label; + + std::mutex debug_start_mutex; + std::pair > debug_stop; + boost::filesystem::path debug_last_stop_file_path; + std::mutex debug_stop_mutex; + Glib::Dispatcher debug_update_stop; + std::string debug_status; + std::mutex debug_status_mutex; + Glib::Dispatcher debug_update_status; + + Tooltips debug_variable_tooltips; + + std::unique_ptr get_cmake(); + std::unordered_map project_run_arguments; + std::unordered_map debug_run_arguments; void configure(); void set_menu_actions(); @@ -43,6 +64,7 @@ private: std::string last_search; std::string last_replace; std::string last_run_command; + std::string last_run_debug_command; bool case_sensitive_search=true; bool regex_search=false; bool search_entry_shown=false;