Browse Source

Merge pull request #162 from eidheim/master

Project cleanup - easier to support compile/run/debug for other languages
merge-requests/365/head
Jørgen Lien Sellæg 10 years ago
parent
commit
6d4570991e
  1. 2
      docs/install.md
  2. 14
      src/CMakeLists.txt
  3. 10
      src/cmake.cc
  4. 26
      src/config.cc
  5. 15
      src/config.h
  6. 83
      src/debug.h
  7. 64
      src/debug_clang.cc
  8. 85
      src/debug_clang.h
  9. 3
      src/dialogs.cc
  10. 26
      src/directories.cc
  11. 3
      src/directories.h
  12. 30
      src/dispatcher.cc
  13. 20
      src/dispatcher.h
  14. 2
      src/files.h
  15. 13
      src/juci.cc
  16. 6
      src/menu.cc
  17. 6
      src/menu.h
  18. 25
      src/notebook.cc
  19. 9
      src/notebook.h
  20. 544
      src/project.cc
  21. 127
      src/project.h
  22. 5
      src/selectiondialog.cc
  23. 1
      src/selectiondialog.h
  24. 216
      src/source_clang.cc
  25. 18
      src/source_clang.h
  26. 86
      src/terminal.cc
  27. 13
      src/terminal.h
  28. 526
      src/window.cc
  29. 27
      src/window.h

2
docs/install.md

@ -86,7 +86,7 @@ make install
```
##Windows with MSYS2 (https://msys2.github.io/)
**MSYS2 does not yet support lldb, but you can still compile juCi++ without debug support. Also for the time being, MSYS2 must be installed in the default MSYS2 folder (C:\msys64 or C:\msys32).**
**MSYS2 does not yet support lldb, but you can still compile juCi++ without debug support.**
Note that juCi++ must be run in a MinGW Shell (for instance MinGW-w64 Win64 Shell).

14
src/CMakeLists.txt

@ -10,21 +10,17 @@ if(APPLE)
set(ENV{PKG_CONFIG_PATH} "$ENV{PKG_CONFIG_PATH}:/usr/local/lib/pkgconfig:/opt/X11/lib/pkgconfig")
endif()
if(UNIX) #Checking if compiling on Ubuntu that has a buggy menu system
if(UNIX) #Checking if compiling on Ubuntu that for instance has a buggy menu system
find_program(LSB_RELEASE_BIN lsb_release)
if(LSB_RELEASE_BIN)
execute_process(COMMAND ${LSB_RELEASE_BIN} -is
OUTPUT_VARIABLE DISTRIBUTION OUTPUT_STRIP_TRAILING_WHITESPACE)
if((DISTRIBUTION STREQUAL Ubuntu) OR (DISTRIBUTION STREQUAL LinuxMint))
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DJUCI_UBUNTU_BUGGED_MENU")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DJUCI_UBUNTU")
endif()
endif()
endif()
if(MSYS)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DMSYS_PROCESS_USE_SH -DJUCI_CMAKE_INSTALL_PREFIX=\\\"${CMAKE_INSTALL_PREFIX}\\\"")
endif()
INCLUDE(FindPkgConfig)
find_package(LibClang REQUIRED)
@ -84,6 +80,10 @@ set(source_files juci.h
cmake.h
cmake.cc
dialogs.cc
project.h
project.cc
dispatcher.h
dispatcher.cc
../libclangmm/src/CodeCompleteResults.cc
../libclangmm/src/CompilationDatabase.cc
@ -103,7 +103,7 @@ set(source_files juci.h
../tiny-process-library/process.cpp)
if(LIBLLDB_FOUND)
list(APPEND source_files debug.h debug.cc)
list(APPEND source_files debug_clang.h debug_clang.cc)
endif()
if(MSYS)

10
src/cmake.cc

@ -36,7 +36,7 @@ CMake::CMake(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;
boost::filesystem::path default_build_path=Config::get().project.default_build_path;
const std::string path_variable_project_directory_name="<project_directory_name>";
size_t pos=0;
@ -56,7 +56,7 @@ boost::filesystem::path CMake::get_default_build_path(const boost::filesystem::p
}
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;
boost::filesystem::path debug_build_path=Config::get().project.debug_build_path;
const std::string path_variable_project_directory_name="<project_directory_name>";
size_t pos=0;
@ -72,7 +72,7 @@ boost::filesystem::path CMake::get_debug_build_path(const boost::filesystem::pat
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;
auto default_build_path=Config::get().project.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();
@ -112,7 +112,7 @@ bool CMake::create_default_build(const boost::filesystem::path &project_path, bo
auto compile_commands_path=default_build_path/"compile_commands.json";
Dialog::Message message("Creating/updating default build");
auto exit_status=Terminal::get().process(Config::get().terminal.cmake_command+" "+
auto exit_status=Terminal::get().process(Config::get().project.cmake_command+" "+
filesystem::escape_argument(project_path)+" -DCMAKE_EXPORT_COMPILE_COMMANDS=ON", default_build_path);
message.hide();
if(exit_status==EXIT_SUCCESS) {
@ -162,7 +162,7 @@ bool CMake::create_debug_build(const boost::filesystem::path &project_path) {
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+" "+
auto exit_status=Terminal::get().process(Config::get().project.cmake_command+" "+
filesystem::escape_argument(project_path)+" -DCMAKE_BUILD_TYPE=Debug", debug_build_path);
if(message)
message->hide();

26
src/config.cc

@ -5,6 +5,7 @@
#include <iostream>
#include "filesystem.h"
#include "terminal.h"
#include <algorithm>
Config::Config() {
std::vector<std::string> environment_variables = {"JUCI_HOME", "HOME", "AppData"};
@ -25,6 +26,17 @@ Config::Config() {
searched_envs+="]";
throw std::runtime_error("One of these environment variables needs to point to a writable directory to save configuration: " + searched_envs);
}
#ifdef _WIN32
auto env_WD=std::getenv("WD");
auto env_MSYSTEM=std::getenv("MSYSTEM");
if(env_WD!=NULL && env_MSYSTEM!=NULL) {
terminal.msys2_mingw_path=boost::filesystem::path(env_WD).parent_path().parent_path().parent_path();
std::string msystem=env_MSYSTEM;
std::transform(msystem.begin(), msystem.end(), msystem.begin(), ::tolower);
terminal.msys2_mingw_path/=msystem;
}
#endif
}
void Config::load() {
@ -84,15 +96,15 @@ void Config::retrieve_config() {
window.theme_variant=cfg.get<std::string>("gtk_theme.variant");
window.version = cfg.get<std::string>("version");
window.default_size = {cfg.get<int>("default_window_size.width"), cfg.get<int>("default_window_size.height")};
window.save_on_compile_or_run=cfg.get<bool>("project.save_on_compile_or_run");
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");
project.save_on_compile_or_run=cfg.get<bool>("project.save_on_compile_or_run");
project.default_build_path=cfg.get<std::string>("project.default_build_path");
project.debug_build_path=cfg.get<std::string>("project.debug_build_path");
project.make_command=cfg.get<std::string>("project.make_command");
project.cmake_command=cfg.get<std::string>("project.cmake_command");
terminal.clang_format_command=cfg.get<std::string>("project.clang_format_command", "clang-format");
terminal.history_size=cfg.get<int>("terminal_history_size");
terminal.clang_format_command="clang-format";
#ifdef __linux
if(terminal.clang_format_command=="clang-format" &&
!boost::filesystem::exists("/usr/bin/clang-format") && !boost::filesystem::exists("/usr/local/bin/clang-format")) {

15
src/config.h

@ -20,17 +20,25 @@ public:
std::string theme_variant;
std::string version;
std::pair<int, int> default_size;
bool save_on_compile_or_run;
};
class Terminal {
public:
std::string clang_format_command;
int history_size;
#ifdef _WIN32
boost::filesystem::path msys2_mingw_path;
#endif
};
class Project {
public:
std::string default_build_path;
std::string debug_build_path;
std::string cmake_command;
std::string make_command;
std::string clang_format_command;
int history_size;
bool save_on_compile_or_run;
};
class Source {
@ -74,6 +82,7 @@ public:
Menu menu;
Window window;
Terminal terminal;
Project project;
Source source;
const boost::filesystem::path& juci_home_path() const { return home; }

83
src/debug.h

@ -1,83 +0,0 @@
#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

64
src/debug.cc → src/debug_clang.cc

@ -1,4 +1,4 @@
#include "debug.h"
#include "debug_clang.h"
#include <stdio.h>
#ifdef __APPLE__
#include <stdlib.h>
@ -27,7 +27,7 @@ void log(const char *msg, void *) {
std::cout << "debugger log: " << msg << std::endl;
}
Debug::Debug(): state(lldb::StateType::eStateInvalid), buffer_size(131072) {
Debug::Clang::Clang(): 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))
@ -35,8 +35,8 @@ Debug::Debug(): state(lldb::StateType::eStateInvalid), buffer_size(131072) {
#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,
void Debug::Clang::start(const std::string &command, const boost::filesystem::path &path,
const 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) {
@ -88,14 +88,12 @@ void Debug::start(const std::string &command, const boost::filesystem::path &pat
}
//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;
}
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;
}
}
@ -216,14 +214,14 @@ void Debug::start(const std::string &command, const boost::filesystem::path &pat
});
}
void Debug::continue_debug() {
void Debug::Clang::continue_debug() {
event_mutex.lock();
if(state==lldb::StateType::eStateStopped)
process->Continue();
event_mutex.unlock();
}
void Debug::stop() {
void Debug::Clang::stop() {
event_mutex.lock();
if(state==lldb::StateType::eStateRunning) {
auto error=process->Stop();
@ -233,7 +231,7 @@ void Debug::stop() {
event_mutex.unlock();
}
void Debug::kill() {
void Debug::Clang::kill() {
event_mutex.lock();
if(process) {
auto error=process->Kill();
@ -243,7 +241,7 @@ void Debug::kill() {
event_mutex.unlock();
}
void Debug::step_over() {
void Debug::Clang::step_over() {
event_mutex.lock();
if(state==lldb::StateType::eStateStopped) {
process->GetSelectedThread().StepOver();
@ -251,7 +249,7 @@ void Debug::step_over() {
event_mutex.unlock();
}
void Debug::step_into() {
void Debug::Clang::step_into() {
event_mutex.lock();
if(state==lldb::StateType::eStateStopped) {
process->GetSelectedThread().StepInto();
@ -259,7 +257,7 @@ void Debug::step_into() {
event_mutex.unlock();
}
void Debug::step_out() {
void Debug::Clang::step_out() {
event_mutex.lock();
if(state==lldb::StateType::eStateStopped) {
process->GetSelectedThread().StepOut();
@ -267,7 +265,7 @@ void Debug::step_out() {
event_mutex.unlock();
}
std::pair<std::string, std::string> Debug::run_command(const std::string &command) {
std::pair<std::string, std::string> Debug::Clang::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) {
@ -280,7 +278,7 @@ std::pair<std::string, std::string> Debug::run_command(const std::string &comman
return command_return;
}
std::vector<Debug::Frame> Debug::get_backtrace() {
std::vector<Debug::Clang::Frame> Debug::Clang::get_backtrace() {
std::vector<Frame> backtrace;
event_mutex.lock();
if(state==lldb::StateType::eStateStopped) {
@ -317,8 +315,8 @@ std::vector<Debug::Frame> Debug::get_backtrace() {
return backtrace;
}
std::vector<Debug::Variable> Debug::get_variables() {
std::vector<Debug::Variable> variables;
std::vector<Debug::Clang::Variable> Debug::Clang::get_variables() {
std::vector<Debug::Clang::Variable> variables;
event_mutex.lock();
if(state==lldb::StateType::eStateStopped) {
for(uint32_t c_t=0;c_t<process->GetNumThreads();c_t++) {
@ -332,7 +330,7 @@ std::vector<Debug::Variable> Debug::get_variables() {
auto declaration=value.GetDeclaration();
if(declaration.IsValid()) {
Debug::Variable variable;
Debug::Clang::Variable variable;
variable.thread_index_id=thread.GetIndexID();
variable.frame_index=c_f;
@ -361,7 +359,7 @@ std::vector<Debug::Variable> Debug::get_variables() {
return variables;
}
void Debug::select_frame(uint32_t frame_index, uint32_t thread_index_id) {
void Debug::Clang::select_frame(uint32_t frame_index, uint32_t thread_index_id) {
event_mutex.lock();
if(state==lldb::StateType::eStateStopped) {
if(thread_index_id!=0)
@ -371,13 +369,13 @@ void Debug::select_frame(uint32_t frame_index, uint32_t thread_index_id) {
event_mutex.unlock();
}
void Debug::delete_debug() {
void Debug::Clang::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 Debug::Clang::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) {
@ -419,7 +417,7 @@ std::string Debug::get_value(const std::string &variable, const boost::filesyste
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 Debug::Clang::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) {
@ -443,7 +441,7 @@ std::string Debug::get_return_value(const boost::filesystem::path &file_path, un
return return_value;
}
bool Debug::is_invalid() {
bool Debug::Clang::is_invalid() {
bool invalid;
event_mutex.lock();
invalid=state==lldb::StateType::eStateInvalid;
@ -451,7 +449,7 @@ bool Debug::is_invalid() {
return invalid;
}
bool Debug::is_stopped() {
bool Debug::Clang::is_stopped() {
bool stopped;
event_mutex.lock();
stopped=state==lldb::StateType::eStateStopped;
@ -459,7 +457,7 @@ bool Debug::is_stopped() {
return stopped;
}
bool Debug::is_running() {
bool Debug::Clang::is_running() {
bool running;
event_mutex.lock();
running=state==lldb::StateType::eStateRunning;
@ -467,7 +465,7 @@ bool Debug::is_running() {
return running;
}
void Debug::add_breakpoint(const boost::filesystem::path &file_path, int line_nr) {
void Debug::Clang::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())
@ -476,7 +474,7 @@ void Debug::add_breakpoint(const boost::filesystem::path &file_path, int line_nr
event_mutex.unlock();
}
void Debug::remove_breakpoint(const boost::filesystem::path &file_path, int line_nr, int line_count) {
void Debug::Clang::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();
@ -503,7 +501,7 @@ void Debug::remove_breakpoint(const boost::filesystem::path &file_path, int line
event_mutex.unlock();
}
void Debug::write(const std::string &buffer) {
void Debug::Clang::write(const std::string &buffer) {
event_mutex.lock();
if(state==lldb::StateType::eStateRunning) {
process->PutSTDIN(buffer.c_str(), buffer.size());

85
src/debug_clang.h

@ -0,0 +1,85 @@
#ifndef JUCI_DEBUG_CLANG_H_
#define JUCI_DEBUG_CLANG_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>
namespace Debug {
class Clang {
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:
Clang();
public:
static Clang &get() {
static Clang singleton;
return singleton;
}
void start(const std::string &command, const boost::filesystem::path &path="",
const std::vector<std::pair<boost::filesystem::path, int> > &breakpoints={},
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

3
src/dialogs.cc

@ -1,5 +1,6 @@
#include "dialogs.h"
#include "window.h"
#include "notebook.h"
#include <cmath>
namespace sigc {
@ -34,7 +35,7 @@ std::string Dialog::gtk_dialog(const std::string &title,
dialog.set_transient_for(Window::get());
auto current_path=Window::get().notebook.get_current_folder();
auto current_path=Notebook::get().get_current_folder();
boost::system::error_code ec;
if(current_path.empty())
current_path=boost::filesystem::current_path(ec);

26
src/directories.cc

@ -22,6 +22,8 @@ namespace sigc {
}
Directories::Directories() : Gtk::TreeView(), stop_update_thread(false) {
this->set_enable_tree_lines(true);
tree_store = Gtk::TreeStore::create(column_record);
set_model(tree_store);
append_column("", column_record.name);
@ -78,16 +80,6 @@ Directories::Directories() : Gtk::TreeView(), stop_update_thread(false) {
}
});
update_dispatcher.connect([this](){
update_mutex.lock();
for(auto &path: update_paths) {
if(last_write_times.count(path)>0)
add_path(path, last_write_times.at(path).first);
}
update_paths.clear();
update_mutex.unlock();
});
update_thread=std::thread([this](){
while(!stop_update_thread) {
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
@ -105,8 +97,17 @@ Directories::Directories() : Gtk::TreeView(), stop_update_thread(false) {
else
it=last_write_times.erase(it);
}
if(update_paths.size()>0)
update_dispatcher();
if(update_paths.size()>0) {
dispatcher.push([this] {
update_mutex.lock();
for(auto &path: update_paths) {
if(last_write_times.count(path)>0)
add_path(path, last_write_times.at(path).first);
}
update_paths.clear();
update_mutex.unlock();
});
}
}
update_mutex.unlock();
}
@ -116,6 +117,7 @@ Directories::Directories() : Gtk::TreeView(), stop_update_thread(false) {
Directories::~Directories() {
stop_update_thread=true;
update_thread.join();
dispatcher.disconnect();
}
void Directories::open(const boost::filesystem::path& dir_path) {

3
src/directories.h

@ -9,6 +9,7 @@
#include <thread>
#include <mutex>
#include <atomic>
#include "dispatcher.h"
class Directories : public Gtk::TreeView {
public:
@ -51,7 +52,7 @@ private:
std::mutex update_mutex;
std::thread update_thread;
std::atomic<bool> stop_update_thread;
Glib::Dispatcher update_dispatcher;
Dispatcher dispatcher;
std::vector<std::string> update_paths;
};

30
src/dispatcher.cc

@ -0,0 +1,30 @@
#include "dispatcher.h"
Dispatcher::Dispatcher() {
connection=dispatcher.connect([this] {
functions_mutex.lock();
for(auto &function: functions) {
function();
}
functions.clear();
functions_mutex.unlock();
});
}
Dispatcher::~Dispatcher() {
disconnect();
functions_mutex.lock();
functions.clear();
functions_mutex.unlock();
}
void Dispatcher::push(std::function<void()> &&function) {
functions_mutex.lock();
functions.emplace_back(function);
functions_mutex.unlock();
dispatcher();
}
void Dispatcher::disconnect() {
connection.disconnect();
}

20
src/dispatcher.h

@ -0,0 +1,20 @@
#ifndef DISPATCHER_H_
#define DISPATCHER_H_
#include <gtkmm.h>
#include <mutex>
#include <vector>
class Dispatcher {
private:
std::vector<std::function<void()>> functions;
std::mutex functions_mutex;
Glib::Dispatcher dispatcher;
sigc::connection connection;
public:
Dispatcher();
~Dispatcher();
void push(std::function<void()> &&function);
void disconnect();
};
#endif /* DISPATCHER_H_ */

2
src/files.h

@ -125,7 +125,7 @@ const std::string configjson =
" \"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"
" \"cmake_command\": \"cmake -G\\\"MSYS Makefiles\\\"\",\n"
#else
" \"cmake_command\": \"cmake\",\n"
#endif

13
src/juci.cc

@ -1,5 +1,6 @@
#include "juci.h"
#include "window.h"
#include "notebook.h"
#include "directories.h"
#include "menu.h"
#include "config.h"
@ -68,7 +69,7 @@ void Application::on_activate() {
}
for(auto &file: files)
Window::get().notebook.open(file);
Notebook::get().open(file);
for(auto &error: errors)
Terminal::get().print(error, true);
@ -79,16 +80,12 @@ void Application::on_startup() {
Menu::get().build();
auto object = Menu::get().builder->get_object("juci-menu");
auto juci_menu = Glib::RefPtr<Gio::Menu>::cast_dynamic(object);
object = Menu::get().builder->get_object("window-menu");
auto window_menu = Glib::RefPtr<Gio::Menu>::cast_dynamic(object);
if (!juci_menu || !window_menu) {
if (!Menu::get().juci_menu || !Menu::get().window_menu) {
std::cerr << "Menu not found." << std::endl;
}
else {
set_app_menu(juci_menu);
set_menubar(window_menu);
set_app_menu(Menu::get().juci_menu);
set_menubar(Menu::get().window_menu);
}
}

6
src/menu.cc

@ -7,7 +7,7 @@
Menu::Menu() {
auto accels=Config::get().menu.keys;
for(auto &accel: accels) {
#ifdef JUCI_UBUNTU_BUGGED_MENU
#ifdef JUCI_UBUNTU
size_t pos=0;
std::string second=accel.second;
while((pos=second.find('<', pos))!=std::string::npos) {
@ -404,6 +404,10 @@ void Menu::build() {
try {
builder->add_from_string(ui_xml);
auto object = Menu::get().builder->get_object("juci-menu");
juci_menu = Glib::RefPtr<Gio::Menu>::cast_dynamic(object);
object = Menu::get().builder->get_object("window-menu");
window_menu = Glib::RefPtr<Gio::Menu>::cast_dynamic(object);
}
catch (const Glib::Error &ex) {
std::cerr << "building menu failed: " << ex.what();

6
src/menu.h

@ -19,8 +19,12 @@ public:
void set_keys();
void build();
Glib::RefPtr<Gtk::Builder> builder;
Glib::RefPtr<Gio::Menu> juci_menu;
Glib::RefPtr<Gio::Menu> window_menu;
private:
Glib::RefPtr<Gtk::Builder> builder;
std::string ui_xml;
};
#endif // JUCI_MENU_H_

25
src/notebook.cc

@ -27,31 +27,31 @@ namespace sigc {
Notebook::TabLabel::TabLabel(const std::string &title) : Gtk::Box(Gtk::ORIENTATION_HORIZONTAL) {
set_can_focus(false);
label.set_text(title);
label.set_text(title+' ');
label.set_can_focus(false);
button.set_image_from_icon_name("window-close-symbolic", Gtk::ICON_SIZE_MENU);
button.set_can_focus(false);
button.set_relief(Gtk::ReliefStyle::RELIEF_NONE);
//Based on http://www.micahcarrick.com/gtk-notebook-tabs-with-close-button.html
std::string data = ".button {\n"
"-GtkButton-default-border : 0px;\n"
"-GtkButton-default-outside-border : 0px;\n"
"-GtkButton-inner-border: 0px;\n"
"-GtkWidget-focus-line-width : 0px;\n"
"-GtkWidget-focus-padding : 0px;\n"
"padding: 0px;\n"
"}";
auto provider = Gtk::CssProvider::create();
provider->load_from_data(data);
provider->load_from_data(".button {border: 0px; outline-width: 0px; margin: 0px; padding: 0px;}");
button.get_style_context()->add_provider(provider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
pack_start(label, Gtk::PACK_SHRINK);
pack_end(button, Gtk::PACK_SHRINK);
show_all();
}
Notebook::Notebook() : Gtk::Notebook(), last_index(-1) {
Gsv::init();
auto provider = Gtk::CssProvider::create();
provider->load_from_data(".notebook {padding: 0px; -GtkNotebook-tab-overlap: 0px;} .notebook tab {border-radius: 5px;padding: 4px;}");
get_style_context()->add_provider(provider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
get_style_context()->set_junction_sides(Gtk::JunctionSides::JUNCTION_BOTTOM);
signal_switch_page().connect([this](Gtk::Widget* page, guint page_num) {
last_index=-1;
});
@ -166,7 +166,9 @@ void Notebook::open(const boost::filesystem::path &file_path) {
get_current_view()->get_buffer()->signal_modified_changed().connect([this, source_view]() {
std::string title=source_view->file_path.filename().string();
if(source_view->get_buffer()->get_modified())
title+="*";
title+='*';
else
title+=' ';
int page=-1;
for(int c=0;c<size();c++) {
if(get_view(c)==source_view) {
@ -370,4 +372,3 @@ bool Notebook::save_modified_dialog(int page) {
return false;
}
}

9
src/notebook.h

@ -16,8 +16,15 @@ class Notebook : public Gtk::Notebook {
Gtk::Label label;
Gtk::Button button;
};
public:
private:
Notebook();
public:
static Notebook &get() {
static Notebook singleton;
return singleton;
}
Source::View* get_view(int page);
size_t get_index(int page);
int size();

544
src/project.cc

@ -0,0 +1,544 @@
#include "project.h"
#include "config.h"
#include "terminal.h"
#include "filesystem.h"
#include <fstream>
#include "menu.h"
#include "notebook.h"
#ifdef JUCI_ENABLE_DEBUG
#include "debug_clang.h"
#endif
std::unordered_map<std::string, std::string> Project::run_arguments;
std::unordered_map<std::string, std::string> Project::debug_run_arguments;
std::atomic<bool> Project::compiling;
std::atomic<bool> Project::debugging;
std::pair<boost::filesystem::path, std::pair<int, int> > Project::debug_stop;
boost::filesystem::path Project::debug_last_stop_file_path;
std::unique_ptr<Project::Language> Project::current_language;
void Project::debug_update_status(const std::string &debug_status) {
if(debug_status.empty())
debug_status_label().set_text("");
else
debug_status_label().set_text("debug: "+debug_status);
auto &menu=Menu::get();
menu.actions["debug_stop"]->set_enabled(!debug_status.empty());
menu.actions["debug_kill"]->set_enabled(!debug_status.empty());
menu.actions["debug_step_over"]->set_enabled(!debug_status.empty());
menu.actions["debug_step_into"]->set_enabled(!debug_status.empty());
menu.actions["debug_step_out"]->set_enabled(!debug_status.empty());
menu.actions["debug_backtrace"]->set_enabled(!debug_status.empty());
menu.actions["debug_show_variables"]->set_enabled(!debug_status.empty());
menu.actions["debug_run_command"]->set_enabled(!debug_status.empty());
menu.actions["debug_goto_stop"]->set_enabled(!debug_status.empty());
}
void Project::debug_update_stop() {
for(int c=0;c<Notebook::get().size();c++) {
auto view=Notebook::get().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::get().size();c++) {
auto view=Notebook::get().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().get_current_page()!=-1)
Notebook::get().get_current_view()->get_buffer()->place_cursor(Notebook::get().get_current_view()->get_buffer()->get_insert()->get_iter());
}
std::unique_ptr<Project::Language> Project::get_language() {
if(Notebook::get().get_current_page()!=-1) {
auto language_id=Notebook::get().get_current_view()->language->get_id();
if(language_id=="markdown")
return std::unique_ptr<Project::Language>(new Project::Markdown());
if(language_id=="python")
return std::unique_ptr<Project::Language>(new Project::Python());
if(language_id=="js")
return std::unique_ptr<Project::Language>(new Project::JavaScript());
if(language_id=="html")
return std::unique_ptr<Project::Language>(new Project::HTML());
}
return std::unique_ptr<Project::Language>(new Project::Clang());
}
std::unique_ptr<CMake> Project::Clang::get_cmake() {
boost::filesystem::path path;
if(Notebook::get().get_current_page()!=-1)
path=Notebook::get().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;
}
std::pair<std::string, std::string> Project::Clang::get_run_arguments() {
auto cmake=get_cmake();
if(!cmake)
return {"", ""};
auto project_path=cmake->project_path.string();
auto run_arguments_it=run_arguments.find(project_path);
std::string arguments;
if(run_arguments_it!=run_arguments.end())
arguments=run_arguments_it->second;
if(arguments.empty()) {
auto executable=cmake->get_executable(Notebook::get().get_current_page()!=-1?Notebook::get().get_current_view()->file_path:"").string();
if(executable!="") {
auto project_path=cmake->project_path;
auto build_path=CMake::get_default_build_path(project_path);
if(!build_path.empty()) {
size_t pos=executable.find(project_path.string());
if(pos!=std::string::npos)
executable.replace(pos, project_path.string().size(), build_path.string());
}
arguments=filesystem::escape_argument(executable);
}
else
arguments=filesystem::escape_argument(CMake::get_default_build_path(cmake->project_path));
}
return {project_path, arguments};
}
void Project::Clang::compile() {
auto cmake=get_cmake();
if(!cmake)
return;
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().project.make_command, default_build_path, [this](int exit_status) {
compiling=false;
});
}
void Project::Clang::compile_and_run() {
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=run_arguments.find(project_path.string());
std::string arguments;
if(run_arguments_it!=run_arguments.end())
arguments=run_arguments_it->second;
if(arguments.empty()) {
arguments=cmake->get_executable(Notebook::get().get_current_page()!=-1?Notebook::get().get_current_view()->file_path:"").string();
if(arguments.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 Project Set Run Arguments, or open a source file within a directory where add_executable is set.\n", true);
return;
}
size_t pos=arguments.find(project_path.string());
if(pos!=std::string::npos)
arguments.replace(pos, project_path.string().size(), default_build_path.string());
arguments=filesystem::escape_argument(arguments);
}
compiling=true;
Terminal::get().print("Compiling and running "+arguments+"\n");
Terminal::get().async_process(Config::get().project.make_command, default_build_path, [this, arguments, default_build_path](int exit_status){
compiling=false;
if(exit_status==EXIT_SUCCESS) {
Terminal::get().async_process(arguments, default_build_path, [this, arguments](int exit_status){
Terminal::get().async_print(arguments+" returned: "+std::to_string(exit_status)+'\n');
});
}
});
}
#ifdef JUCI_ENABLE_DEBUG
std::pair<std::string, std::string> Project::Clang::debug_get_run_arguments() {
auto cmake=get_cmake();
if(!cmake)
return {"", ""};
auto project_path=cmake->project_path.string();
auto run_arguments_it=debug_run_arguments.find(project_path);
std::string arguments;
if(run_arguments_it!=debug_run_arguments.end())
arguments=run_arguments_it->second;
if(arguments.empty()) {
auto executable=cmake->get_executable(Notebook::get().get_current_page()!=-1?Notebook::get().get_current_view()->file_path:"").string();
if(executable!="") {
auto project_path=cmake->project_path;
auto build_path=CMake::get_debug_build_path(project_path);
if(!build_path.empty()) {
size_t pos=executable.find(project_path.string());
if(pos!=std::string::npos)
executable.replace(pos, project_path.string().size(), build_path.string());
}
arguments=filesystem::escape_argument(executable);
}
else
arguments=filesystem::escape_argument(CMake::get_debug_build_path(cmake->project_path));
}
return {project_path, arguments};
}
void Project::Clang::debug_start() {
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;
if(run_arguments.empty()) {
run_arguments=cmake->get_executable(Notebook::get().get_current_page()!=-1?Notebook::get().get_current_view()->file_path:"").string();
if(run_arguments.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=run_arguments.find(project_path.string());
if(pos!=std::string::npos)
run_arguments.replace(pos, project_path.string().size(), debug_build_path.string());
run_arguments=filesystem::escape_argument(run_arguments);
}
auto breakpoints=std::make_shared<std::vector<std::pair<boost::filesystem::path, int> > >();
for(int c=0;c<Notebook::get().size();c++) {
auto view=Notebook::get().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 "+run_arguments+"\n");
Terminal::get().async_process(Config::get().project.make_command, debug_build_path, [this, breakpoints, run_arguments, debug_build_path](int exit_status){
if(exit_status!=EXIT_SUCCESS)
debugging=false;
else {
debug_start_mutex.lock();
Debug::Clang::get().start(run_arguments, debug_build_path, *breakpoints, [this, run_arguments](int exit_status){
debugging=false;
Terminal::get().async_print(run_arguments+" returned: "+std::to_string(exit_status)+'\n');
}, [this](const std::string &status) {
dispatcher.push([this, status] {
debug_update_status(status);
});
}, [this](const boost::filesystem::path &file_path, int line_nr, int line_index) {
dispatcher.push([this, file_path, line_nr, line_index] {
Project::debug_stop.first=file_path;
Project::debug_stop.second.first=line_nr;
Project::debug_stop.second.second=line_index;
debug_update_stop();
});
});
debug_start_mutex.unlock();
}
});
}
void Project::Clang::debug_continue() {
Debug::Clang::get().continue_debug();
}
void Project::Clang::debug_stop() {
if(debugging)
Debug::Clang::get().stop();
}
void Project::Clang::debug_kill() {
if(debugging)
Debug::Clang::get().kill();
}
void Project::Clang::debug_step_over() {
if(debugging)
Debug::Clang::get().step_over();
}
void Project::Clang::debug_step_into() {
if(debugging)
Debug::Clang::get().step_into();
}
void Project::Clang::debug_step_out() {
if(debugging)
Debug::Clang::get().step_out();
}
void Project::Clang::debug_backtrace() {
if(debugging && Notebook::get().get_current_page()!=-1) {
auto backtrace=Debug::Clang::get().get_backtrace();
auto view=Notebook::get().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::Clang::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::get().open(frame.file_path);
if(Notebook::get().get_current_page()!=-1) {
auto view=Notebook::get().get_current_view();
Debug::Clang::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().get_current_page()!=-1 && Notebook::get().get_current_view()==view)
view->scroll_to(view->get_buffer()->get_insert(), 0.0, 1.0, 0.5);
}
}
};
view->selection_dialog->show();
}
}
void Project::Clang::debug_show_variables() {
if(debugging && Notebook::get().get_current_page()!=-1) {
auto variables=Debug::Clang::get().get_variables();
auto view=Notebook::get().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::Clang::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::get().open(variable.file_path);
if(Notebook::get().get_current_page()!=-1) {
auto view=Notebook::get().get_current_view();
Debug::Clang::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().get_current_page()!=-1 && Notebook::get().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().get_current_page()!=-1) {
auto view=Notebook::get().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();
}
}
void Project::Clang::debug_run_command(const std::string &command) {
if(debugging) {
auto command_return=Debug::Clang::get().run_command(command);
Terminal::get().async_print(command_return.first);
Terminal::get().async_print(command_return.second, true);
}
}
void Project::Clang::debug_add_breakpoint(const boost::filesystem::path &file_path, int line_nr) {
Debug::Clang::get().add_breakpoint(file_path, line_nr);
}
void Project::Clang::debug_remove_breakpoint(const boost::filesystem::path &file_path, int line_nr, int line_count) {
Debug::Clang::get().remove_breakpoint(file_path, line_nr, line_count);
}
bool Project::Clang::debug_is_running() {
return Debug::Clang::get().is_running();
}
void Project::Clang::debug_write(const std::string &buffer) {
Debug::Clang::get().write(buffer);
}
void Project::Clang::debug_delete() {
debug_start_mutex.lock();
Debug::Clang::get().delete_debug();
debug_start_mutex.unlock();
}
#endif
Project::Markdown::~Markdown() {
if(!last_temp_path.empty()) {
boost::filesystem::remove(last_temp_path);
last_temp_path=boost::filesystem::path();
}
}
void Project::Markdown::compile_and_run() {
if(!last_temp_path.empty()) {
boost::filesystem::remove(last_temp_path);
last_temp_path=boost::filesystem::path();
}
std::stringstream stdin_stream, stdout_stream;
auto exit_status=Terminal::get().process(stdin_stream, stdout_stream, "markdown "+Notebook::get().get_current_view()->file_path.string());
if(exit_status==0) {
boost::system::error_code ec;
auto temp_path=boost::filesystem::temp_directory_path(ec);
if(!ec) {
temp_path/=boost::filesystem::unique_path();
temp_path+=".html";
if(!boost::filesystem::exists(temp_path)) {
last_temp_path=temp_path;
std::ofstream file_stream(temp_path.string(), std::fstream::binary);
file_stream << stdout_stream.rdbuf();
file_stream.close();
auto uri=temp_path.string();
#ifdef __APPLE__
Terminal::get().process("open \""+uri+"\"");
#else
#ifdef __linux
uri="file://"+uri;
#endif
GError* error=NULL;
gtk_show_uri(NULL, uri.c_str(), GDK_CURRENT_TIME, &error);
g_clear_error(&error);
#endif
}
}
}
}
void Project::Python::compile_and_run() {
auto command="python "+Notebook::get().get_current_view()->file_path.string();
Terminal::get().print("Running "+command+"\n");
Terminal::get().async_process(command, Notebook::get().get_current_view()->file_path.parent_path(), [command](int exit_status) {
Terminal::get().async_print(command+" returned: "+std::to_string(exit_status)+'\n');
});
}
void Project::JavaScript::compile_and_run() {
auto command="node "+Notebook::get().get_current_view()->file_path.string();
Terminal::get().print("Running "+command+"\n");
Terminal::get().async_process(command, Notebook::get().get_current_view()->file_path.parent_path(), [command](int exit_status) {
Terminal::get().async_print(command+" returned: "+std::to_string(exit_status)+'\n');
});
}
void Project::HTML::compile_and_run() {
auto uri=Notebook::get().get_current_view()->file_path.string();
#ifdef __APPLE__
Terminal::get().process("open \""+uri+"\"");
#else
#ifdef __linux
uri="file://"+uri;
#endif
GError* error=NULL;
gtk_show_uri(NULL, uri.c_str(), GDK_CURRENT_TIME, &error);
g_clear_error(&error);
#endif
}

127
src/project.h

@ -0,0 +1,127 @@
#ifndef JUCI_PROJECT_H_
#define JUCI_PROJECT_H_
#include <gtkmm.h>
#include "cmake.h"
#include <boost/filesystem.hpp>
#include "directories.h"
#include <atomic>
#include <mutex>
#include "tooltips.h"
#include "dispatcher.h"
#include <iostream>
class Project {
private:
static boost::filesystem::path debug_last_stop_file_path;
public:
static std::unordered_map<std::string, std::string> run_arguments;
static std::unordered_map<std::string, std::string> debug_run_arguments;
static std::atomic<bool> compiling;
static std::atomic<bool> debugging;
static std::pair<boost::filesystem::path, std::pair<int, int> > debug_stop;
static void debug_update_stop();
static void debug_update_status(const std::string &debug_status);
static Gtk::Label &debug_status_label() {
static Gtk::Label label;
return label;
}
class Language {
public:
Language() {}
virtual ~Language() {}
virtual std::pair<std::string, std::string> get_run_arguments() {return {"", ""};}
virtual void compile() {}
virtual void compile_and_run() {}
virtual std::pair<std::string, std::string> debug_get_run_arguments() {return {"", ""};}
Tooltips debug_variable_tooltips;
virtual void debug_start() {}
virtual void debug_continue() {}
virtual void debug_stop() {}
virtual void debug_kill() {}
virtual void debug_step_over() {}
virtual void debug_step_into() {}
virtual void debug_step_out() {}
virtual void debug_backtrace() {}
virtual void debug_show_variables() {}
virtual void debug_run_command(const std::string &command) {}
virtual void debug_add_breakpoint(const boost::filesystem::path &file_path, int line_nr) {}
virtual void debug_remove_breakpoint(const boost::filesystem::path &file_path, int line_nr, int line_count) {}
virtual bool debug_is_running() { return false; }
virtual void debug_write(const std::string &buffer) {}
virtual void debug_delete() {}
};
class Clang : public Language {
private:
Dispatcher dispatcher;
public:
Clang() : Language() {}
~Clang() { dispatcher.disconnect(); }
std::unique_ptr<CMake> get_cmake();
std::pair<std::string, std::string> get_run_arguments() override;
void compile() override;
void compile_and_run() override;
std::mutex debug_start_mutex;
#ifdef JUCI_ENABLE_DEBUG
std::pair<std::string, std::string> debug_get_run_arguments() override;
void debug_start() override;
void debug_continue() override;
void debug_stop() override;
void debug_kill() override;
void debug_step_over() override;
void debug_step_into() override;
void debug_step_out() override;
void debug_backtrace() override;
void debug_show_variables() override;
void debug_run_command(const std::string &command) override;
void debug_add_breakpoint(const boost::filesystem::path &file_path, int line_nr) override;
void debug_remove_breakpoint(const boost::filesystem::path &file_path, int line_nr, int line_count) override;
bool debug_is_running() override;
void debug_write(const std::string &buffer) override;
void debug_delete() override;
#endif
};
class Markdown : public Language {
public:
Markdown() : Language() {}
~Markdown();
boost::filesystem::path last_temp_path;
void compile_and_run() override;
};
class Python : public Language {
public:
Python() : Language() {}
void compile_and_run() override;
};
class JavaScript : public Language {
public:
JavaScript() : Language() {}
void compile_and_run() override;
};
class HTML : public Language {
public:
HTML() : Language() {}
void compile_and_run() override;
};
static std::unique_ptr<Language> get_language();
static std::unique_ptr<Language> current_language;
};
#endif // JUCI_PROJECT_H_

5
src/selectiondialog.cc

@ -38,10 +38,6 @@ void ListViewText::append(const std::string& value) {
new_row->set_value(column_record.text, value);
}
void ListViewText::clear() {
list_store->clear();
}
SelectionDialogBase::SelectionDialogBase(Gtk::TextView& text_view, Glib::RefPtr<Gtk::TextBuffer::Mark> start_mark, bool show_search_entry, bool use_markup): text_view(text_view),
list_view_text(use_markup), start_mark(start_mark), show_search_entry(show_search_entry) {
if(!show_search_entry)
@ -121,7 +117,6 @@ void SelectionDialogBase::hide() {
window->hide();
if(on_hide)
on_hide();
list_view_text.clear();
}
void SelectionDialogBase::move() {

1
src/selectiondialog.h

@ -17,7 +17,6 @@ public:
bool use_markup;
ListViewText(bool use_markup);
void append(const std::string& value);
void clear();
private:
Glib::RefPtr<Gtk::ListStore> list_store;
ColumnRecord column_record;

216
src/source_clang.cc

@ -3,7 +3,7 @@
#include "terminal.h"
#include "cmake.h"
#ifdef JUCI_ENABLE_DEBUG
#include "debug.h"
#include "debug_clang.h"
#endif
namespace sigc {
@ -35,36 +35,6 @@ Source::View(file_path, project_path, language) {
configure();
parsing_in_progress=Terminal::get().print_in_progress("Parsing "+file_path.string());
//GTK-calls must happen in main thread, so the parse_thread
//sends signals to the main thread that it is to call the following functions:
parse_preprocess_connection=parse_preprocess.connect([this]{
auto expected=ParseProcessState::PREPROCESSING;
if(parse_mutex.try_lock()) {
if(parse_process_state.compare_exchange_strong(expected, ParseProcessState::PROCESSING))
parse_thread_buffer=get_buffer()->get_text();
parse_mutex.unlock();
}
else
parse_process_state.compare_exchange_strong(expected, ParseProcessState::STARTING);
});
parse_postprocess_connection=parse_postprocess.connect([this](){
if(parse_mutex.try_lock()) {
auto expected=ParseProcessState::POSTPROCESSING;
if(parse_process_state.compare_exchange_strong(expected, ParseProcessState::IDLE)) {
update_syntax();
update_diagnostics();
parsed=true;
set_status("");
}
parse_mutex.unlock();
}
});
parse_error_connection=parse_error.connect([this](){
Terminal::get().print("Error: failed to reparse "+this->file_path.string()+".\n", true);
set_status("");
set_info("");
parsing_in_progress->cancel("failed");
});
parse_initialize();
get_buffer()->signal_changed().connect([this]() {
@ -140,8 +110,18 @@ void Source::ClangViewParse::parse_initialize() {
if(parse_state!=ParseState::PROCESSING)
break;
auto expected=ParseProcessState::STARTING;
if(parse_process_state.compare_exchange_strong(expected, ParseProcessState::PREPROCESSING))
parse_preprocess();
if(parse_process_state.compare_exchange_strong(expected, ParseProcessState::PREPROCESSING)) {
dispatcher.push([this] {
auto expected=ParseProcessState::PREPROCESSING;
if(parse_mutex.try_lock()) {
if(parse_process_state.compare_exchange_strong(expected, ParseProcessState::PROCESSING))
parse_thread_buffer=get_buffer()->get_text();
parse_mutex.unlock();
}
else
parse_process_state.compare_exchange_strong(expected, ParseProcessState::STARTING);
});
}
else if (parse_process_state==ParseProcessState::PROCESSING && parse_mutex.try_lock()) {
auto status=clang_tu->ReparseTranslationUnit(parse_thread_buffer.raw());
parsing_in_progress->done("done");
@ -150,7 +130,18 @@ void Source::ClangViewParse::parse_initialize() {
if(parse_process_state.compare_exchange_strong(expected, ParseProcessState::POSTPROCESSING)) {
clang_tokens=clang_tu->get_tokens(0, parse_thread_buffer.bytes()-1);
parse_mutex.unlock();
parse_postprocess();
dispatcher.push([this] {
if(parse_mutex.try_lock()) {
auto expected=ParseProcessState::POSTPROCESSING;
if(parse_process_state.compare_exchange_strong(expected, ParseProcessState::IDLE)) {
update_syntax();
update_diagnostics();
parsed=true;
set_status("");
}
parse_mutex.unlock();
}
});
}
else
parse_mutex.unlock();
@ -158,7 +149,12 @@ void Source::ClangViewParse::parse_initialize() {
else {
parse_state=ParseState::STOP;
parse_mutex.unlock();
parse_error();
dispatcher.push([this] {
Terminal::get().print("Error: failed to reparse "+this->file_path.string()+".\n", true);
set_status("");
set_info("");
parsing_in_progress->cancel("failed");
});
}
}
}
@ -204,10 +200,8 @@ std::vector<std::string> Source::ClangViewParse::get_compilation_commands() {
arguments.emplace_back("-I/Library/Developer/CommandLineTools/usr/bin/../include/c++/v1"); //Added for OS X 10.11
#endif
#ifdef _WIN32
arguments.emplace_back("-IC:/msys32/mingw32/lib/clang/"+clang_version+"/include");
arguments.emplace_back("-IC:/msys32/mingw64/lib/clang/"+clang_version+"/include");
arguments.emplace_back("-IC:/msys64/mingw32/lib/clang/"+clang_version+"/include");
arguments.emplace_back("-IC:/msys64/mingw64/lib/clang/"+clang_version+"/include");
if(!Config::get().terminal.msys2_mingw_path.empty())
arguments.emplace_back("-I"+(Config::get().terminal.msys2_mingw_path/"lib/clang"/clang_version/"include").string());
#endif
}
arguments.emplace_back("-fretain-comments-from-system-headers");
@ -418,15 +412,15 @@ void Source::ClangViewParse::show_type_tooltips(const Gdk::Rectangle &rectangle)
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()) {
if(Debug::Clang::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);
Glib::ustring debug_value=Debug::Clang::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);
debug_value=Debug::Clang::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(" = ");
@ -713,70 +707,11 @@ Source::ClangViewParse(file_path, project_path, language), autocomplete_state(Au
return false;
});
autocomplete_done_connection=autocomplete_done.connect([this](){
if(autocomplete_state==AutocompleteState::CANCELED) {
set_status("");
soft_reparse();
autocomplete_state=AutocompleteState::IDLE;
}
else if(autocomplete_state==AutocompleteState::RESTARTING) {
set_status("");
soft_reparse();
autocomplete_state=AutocompleteState::IDLE;
autocomplete_restart();
}
else {
autocomplete_dialog_setup();
for (auto &data : autocomplete_data) {
std::string row;
std::string return_value;
for (auto &chunk : data.chunks) {
if(chunk.kind==clang::CompletionChunk_ResultType)
return_value=chunk.chunk;
else if(chunk.kind!=clang::CompletionChunk_Informative)
row+=chunk.chunk;
}
data.chunks.clear();
if (!row.empty()) {
auto row_insert_on_selection=row;
if(!return_value.empty())
row+=" --> " + return_value;
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()) {
get_source_buffer()->begin_user_action();
autocomplete_dialog->show();
}
else
soft_reparse();
}
});
autocomplete_restart_connection=autocomplete_restart.connect([this]() {
autocomplete_check();
});
autocomplete_error_connection=autocomplete_error.connect([this]() {
Terminal::get().print("Error: autocomplete failed, reparsing "+this->file_path.string()+"\n", true);
autocomplete_state=AutocompleteState::CANCELED;
full_reparse();
});
do_delete_object_connection=do_delete_object.connect([this](){
do_delete_object.connect([this](){
if(delete_thread.joinable())
delete_thread.join();
do_delete_object_connection.disconnect();
delete this;
});
do_full_reparse.connect([this](){
parse_initialize();
full_reparse_running=false;
});
}
bool Source::ClangViewAutocomplete::on_key_press_event(GdkEventKey *key) {
@ -847,7 +782,7 @@ void Source::ClangViewAutocomplete::autocomplete_dialog_setup() {
//new autocomplete after for instance when selecting "std::"
auto iter=get_buffer()->get_insert()->get_iter();
if(iter.backward_char() && *iter==':')
autocomplete_restart();
autocomplete_check();
}
}
};
@ -918,8 +853,6 @@ void Source::ClangViewAutocomplete::autocomplete() {
autocomplete_state=AutocompleteState::STARTING;
autocomplete_data.clear();
set_status("autocomplete...");
if(autocomplete_thread.joinable())
autocomplete_thread.join();
@ -938,12 +871,62 @@ void Source::ClangViewAutocomplete::autocomplete() {
parse_mutex.lock();
if(parse_state==ParseState::PROCESSING) {
parse_process_state=ParseProcessState::IDLE;
autocomplete_data=autocomplete_get_suggestions(buffer->raw(), line_nr, column_nr);
auto autocomplete_data=std::make_shared<std::vector<AutoCompleteData> >(autocomplete_get_suggestions(buffer->raw(), line_nr, column_nr));
if(parse_state==ParseState::PROCESSING) {
dispatcher.push([this, autocomplete_data] {
if(autocomplete_state==AutocompleteState::CANCELED) {
set_status("");
soft_reparse();
autocomplete_state=AutocompleteState::IDLE;
}
else if(autocomplete_state==AutocompleteState::RESTARTING) {
set_status("");
soft_reparse();
autocomplete_state=AutocompleteState::IDLE;
autocomplete_check();
}
else {
autocomplete_dialog_setup();
for (auto &data : *autocomplete_data) {
std::string row;
std::string return_value;
for (auto &chunk : data.chunks) {
if(chunk.kind==clang::CompletionChunk_ResultType)
return_value=chunk.chunk;
else if(chunk.kind!=clang::CompletionChunk_Informative)
row+=chunk.chunk;
}
data.chunks.clear();
if (!row.empty()) {
auto row_insert_on_selection=row;
if(!return_value.empty())
row+=" --> " + return_value;
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()) {
get_source_buffer()->begin_user_action();
autocomplete_dialog->show();
}
else
soft_reparse();
}
});
}
else {
dispatcher.push([this] {
Terminal::get().print("Error: autocomplete failed, reparsing "+this->file_path.string()+"\n", true);
autocomplete_state=AutocompleteState::CANCELED;
full_reparse();
});
}
}
if(parse_state==ParseState::PROCESSING)
autocomplete_done();
else
autocomplete_error();
parse_mutex.unlock();
});
}
@ -1017,7 +1000,10 @@ bool Source::ClangViewAutocomplete::full_reparse() {
parse_thread.join();
if(autocomplete_thread.joinable())
autocomplete_thread.join();
do_full_reparse();
dispatcher.push([this] {
parse_initialize();
full_reparse_running=false;
});
});
return true;
}
@ -1479,14 +1465,8 @@ Source::ClangView::ClangView(const boost::filesystem::path &file_path, const boo
}
void Source::ClangView::async_delete() {
dispatcher.disconnect();
delayed_reparse_connection.disconnect();
parse_postprocess_connection.disconnect();
parse_preprocess_connection.disconnect();
parse_error_connection.disconnect();
autocomplete_done_connection.disconnect();
autocomplete_restart_connection.disconnect();
autocomplete_error_connection.disconnect();
do_restart_parse_connection.disconnect();
delayed_tag_similar_tokens_connection.disconnect();
ClangViewAutocomplete::async_delete();
}

18
src/source_clang.h

@ -9,6 +9,7 @@
#include "clangmm.h"
#include "source.h"
#include "terminal.h"
#include "dispatcher.h"
namespace Source {
class ClangViewParse : public View {
@ -30,6 +31,7 @@ namespace Source {
void soft_reparse() override;
protected:
Dispatcher dispatcher;
void parse_initialize();
std::unique_ptr<clang::TranslationUnit> clang_tu;
std::unique_ptr<clang::Tokens> clang_tokens;
@ -53,13 +55,7 @@ namespace Source {
std::mutex parse_mutex;
std::atomic<ParseState> parse_state;
std::atomic<ParseProcessState> parse_process_state;
sigc::connection parse_preprocess_connection;
sigc::connection parse_postprocess_connection;
sigc::connection parse_error_connection;
private:
Glib::Dispatcher parse_preprocess;
Glib::Dispatcher parse_postprocess;
Glib::Dispatcher parse_error;
Glib::ustring parse_thread_buffer;
void update_syntax();
@ -90,29 +86,19 @@ namespace Source {
bool on_key_press_event(GdkEventKey* key) override;
std::thread autocomplete_thread;
sigc::connection autocomplete_done_connection;
sigc::connection autocomplete_restart_connection;
sigc::connection autocomplete_error_connection;
sigc::connection do_delete_object_connection;
sigc::connection do_restart_parse_connection;
private:
std::atomic<AutocompleteState> autocomplete_state;
void autocomplete_dialog_setup();
void autocomplete_check();
void autocomplete();
std::vector<AutoCompleteData> autocomplete_data;
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;
guint last_keyval=0;
std::string prefix;
std::mutex prefix_mutex;
Glib::Dispatcher do_delete_object;
Glib::Dispatcher do_full_reparse;
std::thread delete_thread;
std::thread full_reparse_thread;
bool full_reparse_running=false;

86
src/terminal.cc

@ -2,14 +2,9 @@
#include <iostream>
#include "logging.h"
#include "config.h"
#ifdef JUCI_ENABLE_DEBUG
#include "debug.h"
#endif
#include "project.h"
Terminal::InProgress::InProgress(const std::string& start_msg): stop(false) {
waiting_print.connect([this](){
Terminal::get().async_print(line_nr-1, ".");
});
start(start_msg);
}
@ -25,7 +20,7 @@ void Terminal::InProgress::start(const std::string& msg) {
size_t c=0;
while(!stop) {
if(c%100==0)
waiting_print();
Terminal::get().async_print(line_nr-1, ".");
std::this_thread::sleep_for(std::chrono::milliseconds(10));
c++;
}
@ -49,25 +44,6 @@ void Terminal::InProgress::cancel(const std::string& msg) {
Terminal::Terminal() {
bold_tag=get_buffer()->create_tag();
bold_tag->property_weight()=PANGO_WEIGHT_BOLD;
async_print_dispatcher.connect([this](){
async_print_strings_mutex.lock();
if(async_print_strings.size()>0) {
for(auto &string_bold: async_print_strings)
print(string_bold.first, string_bold.second);
async_print_strings.clear();
}
async_print_strings_mutex.unlock();
});
async_print_on_line_dispatcher.connect([this](){
async_print_on_line_strings_mutex.lock();
if(async_print_on_line_strings.size()>0) {
for(auto &line_string: async_print_on_line_strings)
print(line_string.first, line_string.second);
async_print_on_line_strings.clear();
}
async_print_on_line_strings_mutex.unlock();
});
}
int Terminal::process(const std::string &command, const boost::filesystem::path &path, bool use_pipes) {
@ -228,55 +204,41 @@ size_t Terminal::print(const std::string &message, bool bold){
return static_cast<size_t>(get_buffer()->end().get_line())+deleted_lines;
}
void Terminal::print(size_t line_nr, const std::string &message){
if(line_nr<deleted_lines)
return;
Glib::ustring umessage=message;
Glib::ustring::iterator iter;
while(!umessage.validate(iter)) {
auto next_char_iter=iter;
next_char_iter++;
umessage.replace(iter, next_char_iter, "?");
}
auto end_line_iter=get_buffer()->get_iter_at_line(static_cast<int>(line_nr-deleted_lines));
while(!end_line_iter.ends_line() && end_line_iter.forward_char()) {}
get_buffer()->insert(end_line_iter, umessage);
}
std::shared_ptr<Terminal::InProgress> Terminal::print_in_progress(std::string start_msg) {
std::shared_ptr<Terminal::InProgress> in_progress=std::shared_ptr<Terminal::InProgress>(new Terminal::InProgress(start_msg));
return in_progress;
}
void Terminal::async_print(const std::string &message, bool bold) {
async_print_strings_mutex.lock();
bool dispatch=true;
if(async_print_strings.size()>0)
dispatch=false;
async_print_strings.emplace_back(message, bold);
async_print_strings_mutex.unlock();
if(dispatch)
async_print_dispatcher();
dispatcher.push([this, message, bold] {
Terminal::get().print(message, bold);
});
}
void Terminal::async_print(int line_nr, const std::string &message) {
async_print_on_line_strings_mutex.lock();
bool dispatch=true;
if(async_print_on_line_strings.size()>0)
dispatch=false;
async_print_on_line_strings.emplace_back(line_nr, message);
async_print_on_line_strings_mutex.unlock();
if(dispatch)
async_print_on_line_dispatcher();
void Terminal::async_print(size_t line_nr, const std::string &message) {
dispatcher.push([this, line_nr, message] {
if(line_nr<deleted_lines)
return;
Glib::ustring umessage=message;
Glib::ustring::iterator iter;
while(!umessage.validate(iter)) {
auto next_char_iter=iter;
next_char_iter++;
umessage.replace(iter, next_char_iter, "?");
}
auto end_line_iter=get_buffer()->get_iter_at_line(static_cast<int>(line_nr-deleted_lines));
while(!end_line_iter.ends_line() && end_line_iter.forward_char()) {}
get_buffer()->insert(end_line_iter, umessage);
});
}
bool Terminal::on_key_press_event(GdkEventKey *event) {
processes_mutex.lock();
bool debug_is_running=false;
#ifdef JUCI_ENABLE_DEBUG
debug_is_running=Debug::get().is_running();
debug_is_running=Project::current_language?Project::current_language->debug_is_running():false;
#endif
if(processes.size()>0 || debug_is_running) {
get_buffer()->place_cursor(get_buffer()->end());
@ -298,7 +260,7 @@ bool Terminal::on_key_press_event(GdkEventKey *event) {
stdin_buffer+='\n';
if(debug_is_running) {
#ifdef JUCI_ENABLE_DEBUG
Debug::get().write(stdin_buffer);
Project::current_language->debug_write(stdin_buffer);
#endif
}
else

13
src/terminal.h

@ -9,6 +9,7 @@
#include <atomic>
#include <iostream>
#include "process.hpp"
#include "dispatcher.h"
class Terminal : public Gtk::TextView {
public:
@ -22,7 +23,7 @@ public:
void start(const std::string& msg);
size_t line_nr;
std::atomic<bool> stop;
Glib::Dispatcher waiting_print;
std::thread wait_thread;
};
@ -41,19 +42,13 @@ public:
void kill_async_processes(bool force=false);
size_t print(const std::string &message, bool bold=false);
void print(size_t line_nr, const std::string &message);
std::shared_ptr<InProgress> print_in_progress(std::string start_msg);
void async_print(const std::string &message, bool bold=false);
void async_print(int line_nr, const std::string &message);
void async_print(size_t line_nr, const std::string &message);
protected:
bool on_key_press_event(GdkEventKey *event);
private:
Glib::Dispatcher async_print_dispatcher;
Glib::Dispatcher async_print_on_line_dispatcher;
std::vector<std::pair<std::string, bool> > async_print_strings;
std::vector<std::pair<int, std::string> > async_print_on_line_strings;
std::mutex async_print_strings_mutex;
std::mutex async_print_on_line_strings_mutex;
Dispatcher dispatcher;
Glib::RefPtr<Gtk::TextTag> bold_tag;
std::vector<std::shared_ptr<Process> > processes;

526
src/window.cc

@ -6,9 +6,6 @@
//#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
@ -24,11 +21,13 @@ namespace sigc {
#endif
}
Window::Window() : compiling(false), debugging(false) {
Window::Window() : notebook(Notebook::get()) {
JDEBUG("start");
set_title("juCi++");
set_events(Gdk::POINTER_MOTION_MASK|Gdk::FOCUS_CHANGE_MASK|Gdk::SCROLL_MASK|Gdk::LEAVE_NOTIFY_MASK);
set_menu_actions();
configure();
set_default_size(Config::get().window.default_size.first, Config::get().window.default_size.second);
@ -49,11 +48,12 @@ Window::Window() : compiling(false), debugging(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);
info_and_status_hbox.set_center_widget(Project::debug_status_label());
#else
debug_status_label.set_halign(Gtk::Align::ALIGN_CENTER);
info_and_status_hbox.pack_start(debug_status_label);
Project::debug_status_label().set_halign(Gtk::Align::ALIGN_CENTER);
info_and_status_hbox.pack_start(Project::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);
@ -121,74 +121,6 @@ Window::Window() : compiling(false), debugging(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",
@ -203,22 +135,6 @@ Window::Window() : compiling(false), debugging(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();
@ -591,33 +507,11 @@ void Window::set_menu_actions() {
});
menu.add_action("project_set_run_arguments", [this]() {
auto cmake=get_cmake();
if(!cmake)
auto project_language=Project::get_language();
auto run_arguments=std::make_shared<std::pair<std::string, std::string> >(project_language->get_run_arguments());
if(run_arguments->second.empty())
return;
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());
}
run_arguments=filesystem::escape_argument(executable);
}
else
run_arguments=filesystem::escape_argument(CMake::get_default_build_path(cmake->project_path));
}
entry_box.clear();
entry_box.labels.emplace_back();
auto label_it=entry_box.labels.begin();
@ -625,8 +519,8 @@ void Window::set_menu_actions() {
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.entries.emplace_back(run_arguments->second, [this, run_arguments](const std::string& content){
Project::run_arguments[run_arguments->first]=content;
entry_box.hide();
}, 50);
auto entry_it=entry_box.entries.begin();
@ -637,75 +531,24 @@ void Window::set_menu_actions() {
entry_box.show();
});
menu.add_action("compile_and_run", [this]() {
if(compiling)
if(Project::compiling || Project::debugging)
return;
if(Config::get().window.save_on_compile_or_run)
if(Config::get().project.save_on_compile_or_run)
notebook.save_project_files();
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)
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');
});
}
});
Project::current_language=Project::get_language();
Project::current_language->compile_and_run();
});
menu.add_action("compile", [this]() {
if(compiling)
if(Project::compiling || Project::debugging)
return;
if(Config::get().window.save_on_compile_or_run)
if(Config::get().project.save_on_compile_or_run)
notebook.save_project_files();
auto cmake=get_cmake();
if(!cmake)
return;
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;
});
Project::current_language=Project::get_language();
Project::current_language->compile();
});
menu.add_action("run_command", [this]() {
@ -745,33 +588,11 @@ void Window::set_menu_actions() {
#ifdef JUCI_ENABLE_DEBUG
menu.add_action("debug_set_run_arguments", [this]() {
auto cmake=get_cmake();
if(!cmake)
auto project_language=Project::get_language();
auto run_arguments=std::make_shared<std::pair<std::string, std::string> >(project_language->debug_get_run_arguments());
if(run_arguments->second.empty())
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();
@ -779,258 +600,66 @@ void Window::set_menu_actions() {
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.entries.emplace_back(run_arguments->second, [this, run_arguments](const std::string& content){
Project::debug_run_arguments[run_arguments->first]=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->set_placeholder_text("Debug: Set Run Arguments");
entry_box.buttons.emplace_back("Debug: 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();
if(Project::compiling)
return;
else if(Project::debugging) {
Project::current_language->debug_continue();
return;
}
if(Config::get().window.save_on_compile_or_run)
if(Config::get().project.save_on_compile_or_run)
notebook.save_project_files();
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);
}
}
Project::current_language=Project::get_language();
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();
}
});
Project::current_language->debug_start();
});
menu.add_action("debug_stop", [this]() {
if(debugging) {
Debug::get().stop();
}
if(Project::current_language)
Project::current_language->debug_stop();
});
menu.add_action("debug_kill", [this]() {
if(debugging) {
Debug::get().kill();
}
if(Project::current_language)
Project::current_language->debug_kill();
});
menu.add_action("debug_step_over", [this]() {
if(debugging)
Debug::get().step_over();
if(Project::current_language)
Project::current_language->debug_step_over();
});
menu.add_action("debug_step_into", [this]() {
if(debugging)
Debug::get().step_into();
if(Project::current_language)
Project::current_language->debug_step_into();
});
menu.add_action("debug_step_out", [this]() {
if(debugging)
Debug::get().step_out();
if(Project::current_language)
Project::current_language->debug_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();
}
if(Project::current_language)
Project::current_language->debug_backtrace();
});
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();
}
if(Project::current_language)
Project::current_language->debug_show_variables();
});
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);
}
if(Project::current_language)
Project::current_language->debug_run_command(content);
last_run_debug_command=content;
}
entry_box.hide();
@ -1044,39 +673,33 @@ void Window::set_menu_actions() {
});
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);
}
auto view=notebook.get_current_view();
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(Project::current_language && Project::debugging)
Project::current_language->debug_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(Project::current_language && Project::debugging)
Project::current_language->debug_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(Project::debugging) {
if(!Project::debug_stop.first.empty()) {
notebook.open(Project::debug_stop.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;
int line_nr=Project::debug_stop.second.first-1;
int line_index=Project::debug_stop.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;
@ -1091,11 +714,13 @@ void Window::set_menu_actions() {
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();
Project::debug_update_stop();
}
}
}
});
Project::debug_update_status("");
#endif
menu.add_action("next_tab", [this]() {
@ -1192,9 +817,8 @@ bool Window::on_delete_event(GdkEventAny *event) {
}
Terminal::get().kill_async_processes();
#ifdef JUCI_ENABLE_DEBUG
debug_start_mutex.lock();
Debug::get().delete_debug();
debug_start_mutex.unlock();
if(Project::current_language)
Project::current_language->debug_delete();
#endif
return false;
}

27
src/window.h

@ -1,24 +1,23 @@
#ifndef JUCI_WINDOW_H_
#define JUCI_WINDOW_H_
#include "gtkmm.h"
#include <gtkmm.h>
#include "entrybox.h"
#include "notebook.h"
#include "cmake.h"
#include "tooltips.h"
#include "project.h"
#include <atomic>
class Window : public Gtk::ApplicationWindow {
private:
Window();
Notebook &notebook; //convenience reference
public:
static Window &get() {
static Window singleton;
return singleton;
}
Notebook notebook;
protected:
bool on_key_press_event(GdkEventKey *event) override;
bool on_delete_event(GdkEventAny *event) override;
@ -34,26 +33,6 @@ private:
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();
void activate_menu_items(bool activate=true);

Loading…
Cancel
Save