Browse Source

Merge pull request #153 from cppit/debug_integration

Debug integration
merge-requests/365/head
Jørgen Lien Sellæg 10 years ago
parent
commit
5d9d0fb31a
  1. 2
      .gitignore
  2. 2
      README.md
  3. 75
      docs/install.md
  4. 19
      src/CMakeLists.txt
  5. 143
      src/cmake.cc
  6. 20
      src/cmake.h
  7. 1
      src/config.cc
  8. 1
      src/config.h
  9. 512
      src/debug.cc
  10. 83
      src/debug.h
  11. 1
      src/directories.cc
  12. 18
      src/files.h
  13. 26
      src/filesystem.cc
  14. 3
      src/filesystem.h
  15. 6
      src/juci.cc
  16. 1
      src/juci.h
  17. 81
      src/menu.cc
  18. 11
      src/notebook.cc
  19. 146
      src/selectiondialog.cc
  20. 20
      src/selectiondialog.h
  21. 32
      src/source.cc
  22. 1
      src/source.h
  23. 107
      src/source_clang.cc
  24. 3
      src/source_clang.h
  25. 17
      src/terminal.cc
  26. 3
      src/tooltips.cc
  27. 5
      src/tooltips.h
  28. 651
      src/window.cc
  29. 22
      src/window.h

2
.gitignore vendored

@ -10,3 +10,5 @@
!CMakeLists.txt
!config.json
!*.py
build

2
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)

75
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
```
<!--
## Windows with Cygwin (https://www.cygwin.com/)
**Make sure the PATH environment variable does not include paths to non-Cygwin cmake, make and g++.**
Select and install the following packages from the Cygwin-installer:
```
pkg-config libboost-devel libgtkmm3.0-devel libgtksourceviewmm3.0-devel xinit
```
Then run the following in the Cygwin Terminal:
```sh
git clone https://github.com/cppit/jucipp.git
cd jucipp
cmake .
make
make install
```
Note that we are currently working on a Windows-version without the need of an X-server.
-->
## Run
```sh
juci
```
<!--
#### Windows
Alternatively, you can also include directories and files:
```sh
startxwin /usr/local/bin/juci
juci [directory] [file1 file2 ...]
```
-->

19
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}
)

143
src/cmake.cc

@ -5,6 +5,8 @@
#include "terminal.h"
#include <boost/regex.hpp>
std::unordered_set<std::string> 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="<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="<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="<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<Dialog::Message> message;
message=std::unique_ptr<Dialog::Message>(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;c<executable.second.size();c++) {
if(executable.second[c]==file_path.filename()) {
executable_path=executable.first.parent_path()/executable.second[0];
break;
}
}
}
if(!executable_path.empty())
break;
}
}
if(executable_path.empty() && executables.size()>0 && 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));

20
src/cmake.h

@ -3,20 +3,25 @@
#include <boost/filesystem.hpp>
#include <vector>
#include <unordered_map>
#include <unordered_set>
class CMake {
public:
CMake(const boost::filesystem::path &path);
std::vector<std::pair<boost::filesystem::path, std::vector<std::string> > > 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<boost::filesystem::path> 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<std::pair<boost::filesystem::path, std::vector<std::string> > > get_functions_parameters(const std::string &name);
private:
std::vector<std::string> files;
boost::filesystem::path project_path;
std::unordered_map<std::string, std::string> variables;
private:
void read_files();
void remove_tabs();
void remove_comments();
@ -26,5 +31,6 @@ private:
void parse();
std::vector<std::string> get_function_parameters(std::string &data);
bool parsed=false;
static std::unordered_set<std::string> debug_build_needed;
};
#endif //JUCI_CMAKE_H_

1
src/config.cc

@ -86,6 +86,7 @@ void Config::retrieve_config() {
window.default_size = {cfg.get<int>("default_window_size.width"), cfg.get<int>("default_window_size.height")};
terminal.default_build_path=cfg.get<std::string>("project.default_build_path");
terminal.debug_build_path=cfg.get<std::string>("project.debug_build_path");
terminal.make_command=cfg.get<std::string>("project.make_command");
terminal.cmake_command=cfg.get<std::string>("project.cmake_command");
terminal.history_size=cfg.get<int>("terminal_history_size");

1
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;

512
src/debug.cc

@ -0,0 +1,512 @@
#include "debug.h"
#include <stdio.h>
#ifdef __APPLE__
#include <stdlib.h>
#endif
#include <boost/filesystem.hpp>
#include <iostream>
#include "terminal.h"
#include "filesystem.h"
#include <lldb/API/SBTarget.h>
#include <lldb/API/SBProcess.h>
#include <lldb/API/SBEvent.h>
#include <lldb/API/SBBreakpoint.h>
#include <lldb/API/SBThread.h>
#include <lldb/API/SBStream.h>
#include <lldb/API/SBDeclaration.h>
#include <lldb/API/SBCommandInterpreter.h>
#include <lldb/API/SBCommandReturnObject.h>
#include <lldb/API/SBBreakpointLocation.h>
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<std::vector<std::pair<boost::filesystem::path, int> > > breakpoints,
std::function<void(int exit_status)> callback,
std::function<void(const std::string &status)> status_callback,
std::function<void(const boost::filesystem::path &file_path, int line_nr, int line_index)> stop_callback) {
if(!debugger) {
lldb::SBDebugger::Initialize();
debugger=std::unique_ptr<lldb::SBDebugger>(new lldb::SBDebugger(lldb::SBDebugger::Create(true, log, nullptr)));
listener=std::unique_ptr<lldb::SBListener>(new lldb::SBListener("juCi++ lldb listener"));
}
//Create executable string and argument array
std::string executable;
std::vector<std::string> 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(c<command.size() && start_pos==std::string::npos && command[c]!=' ')
start_pos=c;
}
const char *argv[arguments.size()+1];
for(size_t c=0;c<arguments.size();c++)
argv[c]=arguments[c].c_str();
argv[arguments.size()]=NULL;
auto target=debugger->CreateTarget(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<lldb::SBProcess>(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;c<process->GetNumThreads();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<std::string, std::string> Debug::run_command(const std::string &command) {
std::pair<std::string, std::string> 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::Frame> Debug::get_backtrace() {
std::vector<Frame> backtrace;
event_mutex.lock();
if(state==lldb::StateType::eStateStopped) {
auto thread=process->GetSelectedThread();
for(uint32_t c_f=0;c_f<thread.GetNumFrames();c_f++) {
Frame backtrace_frame;
auto frame=thread.GetFrameAtIndex(c_f);
backtrace_frame.index=c_f;
auto function_name=frame.GetFunctionName();
if(function_name!=NULL)
backtrace_frame.function_name=function_name;
auto module_filename=frame.GetModule().GetFileSpec().GetFilename();
if(module_filename!=NULL) {
backtrace_frame.module_filename=module_filename;
}
auto line_entry=frame.GetLineEntry();
if(line_entry.IsValid()) {
lldb::SBStream stream;
line_entry.GetFileSpec().GetDescription(stream);
auto column=line_entry.GetColumn();
if(column==0)
column=1;
backtrace_frame.file_path=stream.GetData();
backtrace_frame.line_nr=line_entry.GetLine();
backtrace_frame.line_index=column;
}
backtrace.emplace_back(backtrace_frame);
}
}
event_mutex.unlock();
return backtrace;
}
std::vector<Debug::Variable> Debug::get_variables() {
std::vector<Debug::Variable> variables;
event_mutex.lock();
if(state==lldb::StateType::eStateStopped) {
for(uint32_t c_t=0;c_t<process->GetNumThreads();c_t++) {
auto thread=process->GetThreadAtIndex(c_t);
for(uint32_t c_f=0;c_f<thread.GetNumFrames();c_f++) {
auto frame=thread.GetFrameAtIndex(c_f);
auto values=frame.GetVariables(true, true, true, false);
for(uint32_t value_index=0;value_index<values.GetSize();value_index++) {
lldb::SBStream stream;
auto value=values.GetValueAtIndex(value_index);
auto declaration=value.GetDeclaration();
if(declaration.IsValid()) {
Debug::Variable variable;
variable.thread_index_id=thread.GetIndexID();
variable.frame_index=c_f;
variable.name=value.GetName();
variable.line_nr=declaration.GetLine();
variable.line_index=declaration.GetColumn();
if(variable.line_index==0)
variable.line_index=1;
auto file_spec=declaration.GetFileSpec();
variable.file_path=file_spec.GetDirectory();
variable.file_path/=file_spec.GetFilename();
value.GetDescription(stream);
variable.value=stream.GetData();
variables.emplace_back(variable);
}
}
}
}
}
event_mutex.unlock();
return variables;
}
void Debug::select_frame(uint32_t frame_index, uint32_t thread_index_id) {
event_mutex.lock();
if(state==lldb::StateType::eStateStopped) {
if(thread_index_id!=0)
process->SetSelectedThreadByIndexID(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_index<values.GetSize();value_index++) {
lldb::SBStream stream;
auto value=values.GetValueAtIndex(value_index);
if(value.GetName()==variable) {
auto declaration=value.GetDeclaration();
if(declaration.IsValid()) {
if(declaration.GetLine()==line_nr && (declaration.GetColumn()==0 || declaration.GetColumn()==line_index)) {
auto file_spec=declaration.GetFileSpec();
boost::filesystem::path value_decl_path=file_spec.GetDirectory();
value_decl_path/=file_spec.GetFilename();
if(value_decl_path==file_path) {
value.GetDescription(stream);
variable_value=stream.GetData();
break;
}
}
}
}
}
if(variable_value.empty()) {
//In case a variable is missing file and line number, only do check on name
auto value=frame.FindVariable(variable.c_str());
if(value.IsValid()) {
lldb::SBStream stream;
value.GetDescription(stream);
variable_value=stream.GetData();
}
}
}
event_mutex.unlock();
return variable_value;
}
std::string Debug::get_return_value(const boost::filesystem::path &file_path, unsigned int line_nr, unsigned int line_index) {
std::string return_value;
event_mutex.lock();
if(state==lldb::StateType::eStateStopped) {
auto thread=process->GetSelectedThread();
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_count;line_nr_try++) {
for(uint32_t b_index=0;b_index<target.GetNumBreakpoints();b_index++) {
auto breakpoint=target.GetBreakpointAtIndex(b_index);
for(uint32_t l_index=0;l_index<breakpoint.GetNumLocations();l_index++) {
auto line_entry=breakpoint.GetLocationAtIndex(l_index).GetAddress().GetLineEntry();
if(line_entry.GetLine()==static_cast<uint32_t>(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();
}

83
src/debug.h

@ -0,0 +1,83 @@
#ifndef JUCI_DEBUG_H_
#define JUCI_DEBUG_H_
#include <boost/filesystem.hpp>
#include <unordered_map>
#include <lldb/API/SBDebugger.h>
#include <lldb/API/SBListener.h>
#include <lldb/API/SBProcess.h>
#include <thread>
#include <mutex>
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<std::vector<std::pair<boost::filesystem::path, int> > > breakpoints=nullptr,
std::function<void(int exit_status)> callback=nullptr,
std::function<void(const std::string &status)> status_callback=nullptr,
std::function<void(const boost::filesystem::path &file_path, int line_nr, int line_index)> 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<std::string, std::string> run_command(const std::string &command);
std::vector<Frame> get_backtrace();
std::vector<Variable> 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<lldb::SBDebugger> debugger;
std::unique_ptr<lldb::SBListener> listener;
std::unique_ptr<lldb::SBProcess> process;
std::thread debug_thread;
lldb::StateType state;
std::mutex event_mutex;
size_t buffer_size;
};
#endif

1
src/directories.cc

@ -130,6 +130,7 @@ void Directories::open(const boost::filesystem::path& dir_path) {
update_mutex.unlock();
cmake=std::unique_ptr<CMake>(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]);

18
src/files.h

@ -2,7 +2,7 @@
#define JUCI_FILES_H_
#include <string>
#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\": \"<primary>r\",\n"
" \"source_goto_next_diagnostic\": \"<primary>e\",\n"
" \"source_apply_fix_its\": \"<control>space\",\n"
" \"project_set_run_arguments\": \"\",\n"
" \"compile_and_run\": \"<primary>Return\",\n"
" \"compile\": \"<primary><shift>Return\",\n"
" \"compile_and_run\": \"<primary>Return\",\n"
" \"run_command\": \"<alt>Return\",\n"
" \"kill_last_running\": \"<primary>Escape\",\n"
" \"force_kill_last_running\": \"<primary><shift>Escape\",\n"
" \"debug_set_run_arguments\": \"\",\n"
" \"debug_start_continue\": \"<primary>y\",\n"
" \"debug_stop\": \"<primary><shift>y\",\n"
" \"debug_kill\": \"<primary><shift>k\",\n"
" \"debug_step_over\": \"<primary>j\",\n"
" \"debug_step_into\": \"<primary>t\",\n"
" \"debug_step_out\": \"<primary><shift>t\",\n"
" \"debug_backtrace\": \"<primary><shift>j\",\n"
" \"debug_show_variables\": \"<primary><shift>b\",\n"
" \"debug_run_command\": \"<alt><shift>Return\",\n"
" \"debug_toggle_breakpoint\": \"<primary>b\",\n"
" \"debug_goto_stop\": \"<primary><shift>l\",\n"
#ifdef __linux
" \"next_tab\": \"<primary>Tab\",\n"
" \"previous_tab\": \"<primary><shift>Tab\",\n"
@ -109,6 +123,8 @@ const std::string configjson =
" \"project\": {\n"
" \"default_build_path_comment\": \"Use <project_directory_name> to insert the project top level directory name\",\n"
" \"default_build_path\": \"./build\",\n"
" \"debug_build_path_comment\": \"Use <project_directory_name> to insert the project top level directory name, and <default_build_path> to insert your default_build_path setting.\",\n"
" \"debug_build_path\": \"<default_build_path>/debug\",\n"
#ifdef _WIN32
" \"cmake_command\": \"cmake -G\\\"MSYS Makefiles\\\" -DCMAKE_INSTALL_PREFIX="+JUCI_CMAKE_INSTALL_PREFIX+"\",\n"
#else

26
src/filesystem.cc

@ -135,3 +135,29 @@ bool filesystem::write(const std::string &path, Glib::RefPtr<Gtk::TextBuffer> 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;
}

3
src/filesystem.h

@ -25,5 +25,8 @@ public:
static bool write(const std::string &path, Glib::RefPtr<Gtk::TextBuffer> text_buffer);
static bool write(const boost::filesystem::path &path, Glib::RefPtr<Gtk::TextBuffer> 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_

6
src/juci.cc

@ -32,7 +32,7 @@ int Application::on_command_line(const Glib::RefPtr<Gio::ApplicationCommandLine>
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() {

1
src/juci.h

@ -13,6 +13,7 @@ public:
private:
std::vector<boost::filesystem::path> directories;
std::vector<boost::filesystem::path> files;
std::vector<std::string> errors;
};
#endif // JUCI_JUCI_H_

81
src/menu.cc

@ -239,6 +239,11 @@ Menu::Menu() {
" <attribute name='label' translatable='yes'>_Project</attribute>"
" <section>"
" <item>"
" <attribute name='label' translatable='yes'>_Set _Run _Arguments</attribute>"
" <attribute name='action'>app.project_set_run_arguments</attribute>"
+accels["project_set_run_arguments"]+ //For Ubuntu...
" </item>"
" <item>"
" <attribute name='label' translatable='yes'>_Compile _and _Run</attribute>"
" <attribute name='action'>app.compile_and_run</attribute>"
+accels["compile_and_run"]+ //For Ubuntu...
@ -269,6 +274,82 @@ Menu::Menu() {
" </submenu>"
""
" <submenu>"
" <attribute name='label' translatable='yes'>_Debug</attribute>"
" <section>"
" <item>"
" <attribute name='label' translatable='yes'>_Set _Run _Arguments</attribute>"
" <attribute name='action'>app.debug_set_run_arguments</attribute>"
+accels["debug_set_run_arguments"]+ //For Ubuntu...
" </item>"
" <item>"
" <attribute name='label' translatable='yes'>_Start/_Continue</attribute>"
" <attribute name='action'>app.debug_start_continue</attribute>"
+accels["debug_start_continue"]+ //For Ubuntu...
" </item>"
" <item>"
" <attribute name='label' translatable='yes'>_Stop</attribute>"
" <attribute name='action'>app.debug_stop</attribute>"
+accels["debug_stop"]+ //For Ubuntu...
" </item>"
" <item>"
" <attribute name='label' translatable='yes'>_Kill</attribute>"
" <attribute name='action'>app.debug_kill</attribute>"
+accels["debug_kill"]+ //For Ubuntu...
" </item>"
" </section>"
" <section>"
" <item>"
" <attribute name='label' translatable='yes'>_Step _Over</attribute>"
" <attribute name='action'>app.debug_step_over</attribute>"
+accels["debug_step_over"]+ //For Ubuntu...
" </item>"
" <item>"
" <attribute name='label' translatable='yes'>_Step _Into</attribute>"
" <attribute name='action'>app.debug_step_into</attribute>"
+accels["debug_step_into"]+ //For Ubuntu...
" </item>"
" <item>"
" <attribute name='label' translatable='yes'>_Step _Out</attribute>"
" <attribute name='action'>app.debug_step_out</attribute>"
+accels["debug_step_out"]+ //For Ubuntu...
" </item>"
" </section>"
" <section>"
" <item>"
" <attribute name='label' translatable='yes'>_Backtrace</attribute>"
" <attribute name='action'>app.debug_backtrace</attribute>"
+accels["debug_backtrace"]+ //For Ubuntu...
" </item>"
" <item>"
" <attribute name='label' translatable='yes'>_Show _Variables</attribute>"
" <attribute name='action'>app.debug_show_variables</attribute>"
+accels["debug_show_variables"]+ //For Ubuntu...
" </item>"
" </section>"
" <section>"
" <item>"
" <attribute name='label' translatable='yes'>_Run Command</attribute>"
" <attribute name='action'>app.debug_run_command</attribute>"
+accels["debug_run_command"]+ //For Ubuntu...
" </item>"
" </section>"
" <section>"
" <item>"
" <attribute name='label' translatable='yes'>_Toggle _Breakpoint</attribute>"
" <attribute name='action'>app.debug_toggle_breakpoint</attribute>"
+accels["debug_toggle_breakpoint"]+ //For Ubuntu...
" </item>"
" </section>"
" <section>"
" <item>"
" <attribute name='label' translatable='yes'>_Go _to _Stop</attribute>"
" <attribute name='action'>app.debug_goto_stop</attribute>"
+accels["debug_goto_stop"]+ //For Ubuntu...
" </item>"
" </section>"
" </submenu>"
""
" <submenu>"
" <attribute name='label' translatable='yes'>_Window</attribute>"
" <section>"
" <item>"

11
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::ClangView*>(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::ClangView*>(source_view))
source_clang_view->async_delete();

146
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<Tooltips>(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<Gtk::TextBuffer::Mark> 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<Gtk::TextBuffer::Mark> start_mark, bool show_search_entry, bool use_markup) : SelectionDialogBase(text_view, start_mark, show_search_entry, use_markup) {
std::shared_ptr<std::string> 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<Gtk::TextBuffer::Mark> start_mark) : SelectionDialogBase(text_view, start_mark, false, false) {}
void CompletionDialog::show() {
SelectionDialogBase::show();
CompletionDialog::CompletionDialog(Gtk::TextView& text_view, Glib::RefPtr<Gtk::TextBuffer::Mark> start_mark) : SelectionDialogBase(text_view, start_mark, false, false) {
show_offset=text_view.get_buffer()->get_insert()->get_iter().get_offset();
std::shared_ptr<std::string> 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) {

20
src/selectiondialog.h

@ -3,7 +3,6 @@
#include "gtkmm.h"
#include "logging.h"
#include "tooltips.h"
#include <unordered_map>
class ListViewText : public Gtk::TreeView {
@ -29,19 +28,21 @@ class SelectionDialogBase {
public:
SelectionDialogBase(Gtk::TextView& text_view, Glib::RefPtr<Gtk::TextBuffer::Mark> 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<void()> on_hide;
std::function<void(const std::string& selected, bool hide_window)> on_select;
std::function<void(const std::string &selected)> on_changed;
Glib::RefPtr<Gtk::TextBuffer::Mark> 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<Gtk::Window> window;
@ -49,8 +50,7 @@ protected:
ListViewText list_view_text;
Gtk::Entry search_entry;
bool show_search_entry;
std::unique_ptr<Tooltips> tooltips;
std::unordered_map<std::string, std::string> tooltip_texts;
std::string last_row;
};
@ -58,13 +58,11 @@ class SelectionDialog : public SelectionDialogBase {
public:
SelectionDialog(Gtk::TextView& text_view, Glib::RefPtr<Gtk::TextBuffer::Mark> 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<Gtk::TextBuffer::Mark> start_mark);
void show() override;
bool on_key_release(GdkEventKey* key);
bool on_key_press(GdkEventKey* key);

32
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)

1
src/source.h

@ -87,6 +87,7 @@ namespace Source {
std::unique_ptr<CompletionDialog> autocomplete_dialog;
std::unique_ptr<SelectionDialog> selection_dialog;
Gtk::TextIter get_iter_for_dialog();
sigc::connection delayed_tooltips_connection;
std::function<void(View* view, const std::string &status_text)> on_update_status;

107
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<std::string> 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<clang::CompileCommand> cmds = commands.get_commands();
std::vector<std::string> 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::string, std::string>(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<std::string>(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<SelectionDialog>(new SelectionDialog(*this, get_buffer()->create_mark(iter), true, true));
auto rows=std::make_shared<std::unordered_map<std::string, clang::Offset> >();
auto methods=clang_tokens->get_cxx_methods();

3
src/source_clang.h

@ -101,8 +101,9 @@ namespace Source {
void autocomplete_check();
void autocomplete();
std::vector<AutoCompleteData> autocomplete_data;
std::unordered_map<std::string, std::string> autocomplete_dialog_rows;
std::unordered_map<std::string, std::pair<std::string, std::string> > autocomplete_dialog_rows;
std::vector<AutoCompleteData> 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;

17
src/terminal.cc

@ -2,6 +2,9 @@
#include <iostream>
#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();
}

3
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;
}

5
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<Ts>(params)...);
}
static Gdk::Rectangle drawn_tooltips_rectangle;
private:
private:
std::list<Tooltip> tooltip_list;
};

651
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;c<notebook.size();c++) {
auto view=notebook.get_view(c);
if(view->file_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;c<notebook.size();c++) {
auto view=notebook.get_view(c);
if(view->file_path==debug_stop.first) {
if(debug_stop.second.first-1<view->get_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<CMake> 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<CMake>(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<SelectionDialog>(new SelectionDialog(*current_view, current_view->get_buffer()->create_mark(iter), true, true));
auto rows=std::make_shared<std::unordered_map<std::string, Source::Offset> >();
@ -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;c<executable.second.size();c++) {
if(executable.second[c]==notebook.get_current_view()->file_path.filename()) {
executable_path=executable.first.parent_path()/executable.second[0];
break;
}
}
auto project_path=std::make_shared<boost::filesystem::path>(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;c<executable_path_spaces_fixed.size();c++) {
if(last_char!='\\' && executable_path_spaces_fixed[c]==' ') {
executable_path_spaces_fixed.insert(c, "\\");
c++;
}
last_char=executable_path_spaces_fixed[c];
}
Terminal::get().async_process(executable_path_spaces_fixed, default_build_path, [this, executable_path](int exit_status){
Terminal::get().async_print(executable_path.string()+" returned: "+std::to_string(exit_status)+'\n');
});
}
});
}
else {
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){
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<boost::filesystem::path>(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<std::vector<std::pair<boost::filesystem::path, int> > >();
for(int c=0;c<notebook.size();c++) {
auto view=notebook.get_view(c);
if(project_path==view->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);
}
}
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<SelectionDialog>(new SelectionDialog(*view, view->get_buffer()->create_mark(iter), true, true));
auto rows=std::make_shared<std::unordered_map<std::string, Debug::Frame> >();
if(backtrace.size()==0)
return;
for(auto &frame: backtrace) {
std::string row="<i>"+frame.module_filename+"</i>";
//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+=":<b>"+Glib::Markup::escape_text(file_path)+":"+std::to_string(frame.line_nr)+"</b> - "+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<SelectionDialog>(new SelectionDialog(*view, view->get_buffer()->create_mark(iter), true, true));
auto rows=std::make_shared<std::unordered_map<std::string, Debug::Variable> >();
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)+" - <b>"+Glib::Markup::escape_text(variable.name)+"</b>";
(*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_nr<view->get_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<size_t>(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<bool>(notebook.get_current_view()->rename_similar_tokens) : false);
menu.actions["source_goto_next_diagnostic"]->set_enabled(activate ? static_cast<bool>(notebook.get_current_view()->goto_next_diagnostic) : false);
menu.actions["source_apply_fix_its"]->set_enabled(activate ? static_cast<bool>(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) {}

22
src/window.h

@ -4,6 +4,8 @@
#include "gtkmm.h"
#include "entrybox.h"
#include "notebook.h"
#include "cmake.h"
#include "tooltips.h"
#include <atomic>
class Window : public Gtk::ApplicationWindow {
@ -31,7 +33,26 @@ private:
Gtk::HBox info_and_status_hbox;
Gtk::AboutDialog about;
EntryBox entry_box;
std::atomic<bool> compiling;
std::atomic<bool> debugging;
Gtk::Label debug_status_label;
std::mutex debug_start_mutex;
std::pair<boost::filesystem::path, std::pair<int, int> > 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<CMake> get_cmake();
std::unordered_map<std::string, std::string> project_run_arguments;
std::unordered_map<std::string, std::string> 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;

Loading…
Cancel
Save